Объектная модель Python: от базового object до метаклассов
Создание объекта через пользовательский класс
Как определить собственный тип данных и создать его экземпляр?
Основной способ работы с объектами в Python заключается в определении класса. Класс является шаблоном, на основе которого создаются объекты. Каждый объект имеет свои атрибуты (данные) и методы (функции).
class Person:
def __init__(self, name, age):
self.name = name # атрибут экземпляра
self.age = age
def greet(self):
return f'Привет, меня зовут {self.name} и мне {self.age} лет.'
p = Person('Анна', 25)
print(p.greet())тип данных объект python (тип данных объект (object) в python)
Привет, меня зовут Анна и мне 25 лет.
Типичная ошибка:
Забыть указать self в определении метода. Если написать def greet(): return ... без self, Python выдаст ошибку при вызове метода, так как первый аргумент (экземпляр) не будет принят.
Решение: всегда добавлять self как первый параметр в методы экземпляра.
Как создать объект без явного определения класса с помощью type?
Функция type позволяет динамически создавать классы, а затем их экземпляры. Это удобно для метапрограммирования или когда класс нужен однократно.
MyDynamicClass = type('MyDynamicClass', (object,), {'x': 10, 'show': lambda self: self.x})
obj = MyDynamicClass()
print(obj.x)
print(obj.show())
10 10
Проблема:
При использовании type сложнее добавлять методы, требующие доступа к атрибутам через self, особенно если лямбда-функция не связана с экземпляром. Лучше передавать именованные функции.
Как создать объект с фиксированными полями без написания класса с помощью namedtuple?
Модуль collections предоставляет namedtuple для создания неизменяемых объектов с именованными полями. Это легковесная альтернатива классу, когда не требуется переопределение методов.
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(3, 4)
print(p.x, p.y)
print(p)
# p.x = 5 # ошибка, namedtuple неизменяем
3 4 Point(x=3, y=4)
Ошибка:
Попытка изменить атрибут приведёт к AttributeError. Для изменяемых полей нужно использовать класс или dataclass без frozen=True.
Как сократить код класса с помощью декоратора @dataclass?
Декоратор @dataclass из модуля dataclasses автоматически генерирует методы __init__, __repr__, __eq__ и другие. Это упрощает создание классов, предназначенных для хранения данных.
from dataclasses import dataclass
@dataclass
class Product:
name: str
price: float
quantity: int = 0
item = Product('Книга', 9.99)
print(item)
print(item.price)
Product(name='Книга', price=9.99, quantity=0) 9.99
Проблема:
По умолчанию поля с изменяемыми типами (например, список) не рекомендуется использовать как значения по умолчанию из-за общей ссылки. Нужно применять default_factory.
Как создать объект базового типа object и добавить атрибуты на лету?
Можно взять экземпляр object() и присвоить ему произвольные атрибуты с помощью setattr или прямой записи. Однако такой объект не имеет методов и подходит только для простого хранения данных.
obj = object()
obj.name = 'Тест'
print(obj.name)
# obj.greet() # ошибка, метод не определён
Тест
Ошибка:
У object() нет словаря атрибутов __dict__? На самом деле у простого object есть __dict__ (для хранения атрибутов), но он не поддерживает динамическое добавление атрибутов, если класс использует __slots__. Однако для чистого object это работает. Важно: нельзя вызывать методы, которые не определены.
Углублённые примеры работы с объектами
Как переопределить создание объекта через __new__ для реализации паттерна синглтон?
Метод __new__ вызывается перед __init__ и отвечает за создание экземпляра. Переопределив его, можно контролировать, создаётся ли новый объект или возвращается существующий.
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
s1 = Singleton(10)
s2 = Singleton(20)
print(s1 is s2)
print(s1.value) # 20, так как s1 и s2 один объект
True 20
Проблема:
При использовании синглтона нужно быть осторожным с тем, что __init__ вызывается каждый раз при создании (даже если объект не новый). В примере выше s1.value перезаписывается. Для предотвращения этого можно проверять, инициализирован ли уже объект.
Как ограничить атрибуты экземпляра с помощью __slots__ и уменьшить расход памяти?
Определение __slots__ в классе запрещает создание произвольных атрибутов и заменяет словарь __dict__ на фиксированный набор дескрипторов, что экономит память.
class Point:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
print(p.x, p.y)
# p.z = 3 # AttributeError: 'Point' object has no attribute 'z'
1 2
Ошибка:
Попытка присвоить атрибут, не указанный в __slots__, вызывает AttributeError. Если класс наследуется от другого класса с __slots__, нужно определить свои слоты и, возможно, добавить __dict__ вручную, если нужна динамика.
Как реализовать дескриптор для управления доступом к атрибутам?
Дескрипторы - это объекты, определяющие __get__, __set__ или __delete__. Они позволяют перехватывать доступ к атрибутам класса.
class PositiveNumber:
def __set_name__(self, owner, name):
self.name = name
def __get__(self, instance, owner):
return instance.__dict__[self.name]
def __set__(self, instance, value):
if value <= 0:
raise ValueError('Значение должно быть положительным')
instance.__dict__[self.name] = value
class Order:
quantity = PositiveNumber()
def __init__(self, qty):
self.quantity = qty
o = Order(5)
print(o.quantity)
# o.quantity = -1 # ValueError
5
Как использовать метакласс для настройки создания классов?
Метакласс - это класс, экземплярами которого являются другие классы. Переопределив __new__ или __init__ в метаклассе, можно изменять поведение всех создаваемых классов.
class UpperCaseAttrMeta(type):
def __new__(cls, name, bases, attrs):
new_attrs = {}
for attr_name, attr_value in attrs.items():
if not attr_name.startswith('__'):
new_attrs[attr_name.upper()] = attr_value
else:
new_attrs[attr_name] = attr_value
return super().__new__(cls, name, bases, new_attrs)
class MyConfig(metaclass=UpperCaseAttrMeta):
debug = True
timeout = 30
print(hasattr(MyConfig, 'DEBUG'))
print(MyConfig.DEBUG)
True True
Как реализовать контекстный менеджер в классе?
Объект может использоваться с оператором with, если определяет методы __enter__ и __exit__.
class ManagedFile:
def __init__(self, filename):
self.filename = filename
def __enter__(self):
self.file = open(self.filename, 'w')
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
if self.file:
self.file.close()
with ManagedFile('test.txt') as f:
f.write('Привет, мир!')
print(open('test.txt').read())
Привет, мир!
Как создать итератор на основе класса?
Класс может поддерживать протокол итерации, определив __iter__ и __next__.
class CountDown:
def __init__(self, start):
self.start = start
def __iter__(self):
return self
def __next__(self):
if self.start <= 0:
raise StopIteration
self.start -= 1
return self.start + 1
for num in CountDown(3):
print(num)
3 2 1