Что является дескриптором в Python?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Дескрипторы в Python
Дескриптор (descriptor) — это объект, определяющий поведение атрибутов при доступе, присваивании или удалении. Дескрипторы — это мощный механизм в Python, который позволяет контролировать взаимодействие с атрибутами класса и реализовать такие паттерны как свойства (properties), методы, статические методы и другие.
Протокол дескриптора
Дескриптор — это класс, который реализует один или несколько методов из дескриптор-протокола:
Методы дескриптора:
__get__(self, obj, objtype=None)— вызывается при доступе к атрибуту__set__(self, obj, value)— вызывается при присваивании значения атрибуту__delete__(self, obj)— вызывается при удалении атрибута
Если дескриптор реализует __set__ или __delete__, он называется data descriptor (дескриптор данных). Если реализует только __get__, — это non-data descriptor (дескриптор без данных).
Простой пример дескриптора
class Descriptor:
def __init__(self, name):
self.name = name
self.value = None
def __get__(self, obj, objtype=None):
if obj is None:
return self
print(f"Getting {self.name}")
return self.value
def __set__(self, obj, value):
print(f"Setting {self.name} to {value}")
self.value = value
def __delete__(self, obj):
print(f"Deleting {self.name}")
self.value = None
class MyClass:
attr = Descriptor("attr")
obj = MyClass()
obj.attr = 42 # Setting attr to 42
print(obj.attr) # Getting attr\n42
del obj.attr # Deleting attr
Property — встроенный дескриптор
@property — это встроенный дескриптор для создания вычисляемых свойств:
class User:
def __init__(self, first_name, last_name):
self._first_name = first_name
self._last_name = last_name
@property
def full_name(self):
"""Вычисляемое свойство — read-only"""
return f"{self._first_name} {self._last_name}"
@full_name.setter
def full_name(self, name):
"""Позволяет установить full_name"""
first, last = name.split()
self._first_name = first
self._last_name = last
@full_name.deleter
def full_name(self):
"""Позволяет удалить full_name"""
self._first_name = None
self._last_name = None
user = User("John", "Doe")
print(user.full_name) # John Doe
user.full_name = "Jane Smith"
print(user.full_name) # Jane Smith
del user.full_name
print(user._first_name) # None
Валидирующий дескриптор
class ValidatedString:
def __init__(self, min_length=0, max_length=None):
self.min_length = min_length
self.max_length = max_length
self.data = {}
def __get__(self, obj, objtype=None):
if obj is None:
return self
return self.data.get(id(obj), None)
def __set__(self, obj, value):
if not isinstance(value, str):
raise TypeError("Value must be a string")
if len(value) < self.min_length:
raise ValueError(f"String too short (min: {self.min_length})")
if self.max_length and len(value) > self.max_length:
raise ValueError(f"String too long (max: {self.max_length})")
self.data[id(obj)] = value
class Product:
name = ValidatedString(min_length=1, max_length=100)
description = ValidatedString(max_length=500)
product = Product()
product.name = "Laptop" # OK
product.name = "" # ValueError: String too short
product.name = 123 # TypeError: Value must be a string
Классметоды и статические методы — дескрипторы
@classmethod и @staticmethod — это встроенные дескрипторы:
class MyClass:
class_var = "shared"
@staticmethod
def static_method():
"""Не получает ни self, ни cls"""
return "static"
@classmethod
def class_method(cls):
"""Получает класс вместо экземпляра"""
return f"Called on {cls.__name__}"
def instance_method(self):
"""Обычный метод"""
return "instance"
print(MyClass.static_method()) # static
print(MyClass.class_method()) # Called on MyClass
obj = MyClass()
print(obj.instance_method()) # instance
Дескриптор с использованием слабых ссылок
Для хранения значений отдельно для каждого объекта лучше использовать слабые ссылки:
import weakref
class BoundMethod:
def __init__(self, func):
self.func = func
self.data = weakref.WeakKeyDictionary()
def __get__(self, obj, objtype=None):
if obj is None:
return self
return lambda *args, **kwargs: self.func(obj, *args, **kwargs)
Порядок разрешения атрибутов (MRO)
Когда вы обращаетесь к атрибуту, Python следует этому порядку:
- Data descriptors из класса и его предков
- Атрибуты экземпляра (
obj.__dict__) - Non-data descriptors из класса
- Атрибуты класса
__getattr__()если определён
class DataDescriptor:
def __get__(self, obj, objtype=None):
return "data descriptor"
def __set__(self, obj, value):
pass
class NonDataDescriptor:
def __get__(self, obj, objtype=None):
return "non-data descriptor"
class MyClass:
data_desc = DataDescriptor()
non_data_desc = NonDataDescriptor()
obj = MyClass()
obj.__dict__['data_desc'] = "instance attr"
obj.__dict__['non_data_desc'] = "instance attr"
print(obj.data_desc) # data descriptor (приоритет data descriptor)
print(obj.non_data_desc) # instance attr (приоритет экземпляра)
Практическое применение
ORM (SQLAlchemy) использует дескрипторы:
from sqlalchemy import Column, String, Integer
from sqlalchemy.orm import declarative_base
Base = declarative_base()
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True) # Column — дескриптор
name = Column(String(100))
Заключение
Дескрипторы — это фундаментальный механизм Python, который стоит за свойствами, методами и множеством фреймворков. Понимание дескрипторов позволяет писать более мощный и гибкий код, создавать элегантные API и глубже понимать работу Python.