Практика работы с объектами в Python

Раздел: 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)  # 31

Python работа с объектами (работа с объектами в 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

Работа с объектами в Python - comments

En
Python работа с объектами (python)