Практика работы с объектами в Python
Работа с объектами: основные подходы и эффективные решения
Наиболее эффективное решение: контроль атрибутов через property и использование __slots__ для экономии памяти.
Класс Person хранит имя и возраст. Возраст проверяется через setter, а __slots__ фиксирует атрибуты, уменьшая расход памяти.
class Person:
__slots__ = ('_name', '_age')
def __init__(self, name, age):
self._name = name
self._age = age
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if not isinstance(value, int) or value < 0:
raise ValueError("Age must be a non-negative integer")
self._age = value
@property
def name(self):
return self._name
p = Person("Alice", 30)
print(p.age) # 30
p.age = 31
print(p.age) # 31Python работа с объектами (работа с объектами в python)
Типичная проблема:
При использовании __slots__ попытка добавить новый атрибут (например, p.new_attr = 1) вызывает AttributeError. Решение: если нужна динамика, не применять __slots__ или включить '__dict__' в __slots__.
Вариант 1: Использование dataclasses для автоматической инициализации
Вопрос: Как упростить создание классов с данными без шаблонного кода?
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
def distance_from_origin(self):
return (self.x**2 + self.y**2)**0.5
p = Point(3, 4)
print(p.distance_from_origin()) # 5.0Проблема:
При frozen=True атрибуты становятся неизменяемыми. Решение: использовать frozen=False (по умолчанию) или задать on_setattr.
Вариант 2: namedtuple для легковесных неизменяемых объектов
Вопрос: Как создать неизменяемый объект для хранения данных?
from collections import namedtuple
Car = namedtuple('Car', ['make', 'model', 'year'])
c = Car('Toyota', 'Corolla', 2020)
print(c.make) # Toyota
print(c[0]) # ToyotaПроблема:
Неизменяемость не позволяет изменять поля после создания. Также отсутствуют методы (но можно расширить через наследование).
Вариант 3: Синглтон через __new__
Вопрос: Как гарантировать, что класс имеет только один экземпляр?
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, value):
self.value = value
a = Singleton(10)
b = Singleton(20)
print(a.value) # 20 (так как __init__ вызывается повторно)
print(a is b) # TrueПроблема:
__init__ вызывается каждый раз при создании, что может перезаписывать данные. Решение: разделить создание и инициализацию, выполняя __init__ только при первом вызове.
Вариант 4: Динамические атрибуты через __getattr__ и __setattr__
Вопрос: Как реализовать объект с виртуальными атрибутами, вычисляемыми на лету?
class DynamicObject:
def __init__(self, **kwargs):
object.__setattr__(self, '_data', kwargs)
def __getattr__(self, name):
if name in self._data:
return self._data[name]
raise AttributeError(f"'{type(self).__name__}' has no attribute '{name}'")
def __setattr__(self, name, value):
if name.startswith('_'):
object.__setattr__(self, name, value)
else:
self._data[name] = value
obj = DynamicObject(a=1, b=2)
print(obj.a) # 1
obj.c = 3
print(obj.c) # 3Проблема:
Рекурсия при __setattr__, если случайно вызвать self._data[name] = value без проверки. Решение: всегда использовать object.__setattr__ для служебных атрибутов.
Вариант 5: Дескрипторы для повторного использования логики доступа
Вопрос: Как создать многократно используемый контроллер атрибутов?
class PositiveNumber:
def __set_name__(self, owner, name):
self._name = '_' + name
def __get__(self, obj, objtype=None):
return getattr(obj, self._name, None)
def __set__(self, obj, value):
if not isinstance(value, (int, float)) or value <= 0:
raise ValueError("Value must be positive")
setattr(obj, self._name, value)
class Order:
quantity = PositiveNumber()
price = PositiveNumber()
def __init__(self, quantity, price):
self.quantity = quantity
self.price = price
def total(self):
return self.quantity * self.price
o = Order(5, 10.5)
print(o.total()) # 52.5Проблема:
Дескрипторы требуют понимания их работы и правильной реализации __set_name__ для корректного хранения данных.
Расширенные примеры работы с объектами
Пример 1: Метакласс для автоматической регистрации подклассов
Метакласс RegistryMeta собирает все подклассы Base в словарь.
class RegistryMeta(type):
registry = {}
def __new__(cls, name, bases, dct):
new_class = super().__new__(cls, name, bases, dct)
if name != 'Base':
cls.registry[name] = new_class
return new_class
class Base(metaclass=RegistryMeta):
pass
class A(Base):
pass
class B(Base):
pass
print(RegistryMeta.registry){'A': <class '__main__.A'>, 'B': <class '__main__.B'>}Пример 2: Контекстный менеджер на основе объекта
Класс FileHandler реализует протокол контекстного менеджера для безопасной работы с файлами.
class FileHandler:
def __init__(self, filename, mode):
self.filename = filename
self.mode = mode
self.file = None
def __enter__(self):
self.file = open(self.filename, self.mode)
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
if self.file:
self.file.close()
return False
with FileHandler('test.txt', 'w') as f:
f.write('Hello, World!')Пример 3: Глубокое и поверхностное копирование
Разница между copy.copy и copy.deepcopy на примере вложенных списков.
import copy
class Data:
def __init__(self, items):
self.items = items
original = Data([1, 2, [3, 4]])
shallow = copy.copy(original)
deep = copy.deepcopy(original)
original.items[2].append(5)
print("Original items:", original.items)
print("Shallow items:", shallow.items)
print("Deep items:", deep.items)Original items: [1, 2, [3, 4, 5]] Shallow items: [1, 2, [3, 4, 5]] Deep items: [1, 2, [3, 4]]
Пример 4: Слабые ссылки для предотвращения циклических ссылок
Использование weakref.ref в дереве узлов для избежания утечек памяти.
import weakref
class Node:
def __init__(self, value):
self.value = value
self.parent = None
self.children = []
def add_child(self, child):
self.children.append(child)
child.parent = weakref.ref(self)
parent = Node('parent')
child = Node('child')
parent.add_child(child)
print(child.parent())
del parent
print(child.parent())<__main__.Node object at 0x...> None
Пример 5: Декоратор класса для логгирования доступа к атрибутам
Декоратор logged оборачивает класс, переопределяя __getattribute__ для вывода сообщений.
def logged(cls):
class LoggedClass(cls):
def __getattribute__(self, name):
print(f"Accessing {name}")
return super().__getattribute__(name)
return LoggedClass
@logged
class MyClass:
def __init__(self, value):
self.value = value
def show(self):
return self.value
obj = MyClass(42)
print(obj.value)
print(obj.show())Accessing value 42 Accessing show Accessing value 42
Пример 6: Паттерн Состояние с использованием объектов
Переключение состояния объекта через делегирование вызова объекту состояния.
class State:
def handle(self, context):
pass
class ConcreteStateA(State):
def handle(self, context):
print("State A: переход в B")
context.state = ConcreteStateB()
class ConcreteStateB(State):
def handle(self, context):
print("State B: переход в A")
context.state = ConcreteStateA()
class Context:
def __init__(self, state):
self.state = state
def request(self):
self.state.handle(self)
c = Context(ConcreteStateA())
c.request()
c.request()
c.request()State A: переход в B State B: переход в A State A: переход в B