← Назад к вопросам
Как создать свой дескриптор?
3.0 Senior🔥 61 комментариев
#Python Core#Архитектура и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как создать свой дескриптор
Дескриптор — это объект, который имеет методы __get__, __set__ и/или __delete__. Это позволяет контролировать доступ к атрибутам класса и создавать свойства с настраиваемым поведением.
1. Базовый дескриптор
class Descriptor:
"""Базовый дескриптор"""
def __get__(self, obj, objtype=None):
print("Getting value")
return getattr(obj, '_value', None)
def __set__(self, obj, value):
print(f"Setting value to {value}")
obj._value = value
def __delete__(self, obj):
print("Deleting value")
del obj._value
class MyClass:
x = Descriptor()
obj = MyClass()
obj.x = 10 # Вызывает __set__
print(obj.x) # Вызывает __get__
del obj.x # Вызывает __delete__
2. Non-data дескриптор (только get)
class Lazy:
"""Lazy-loaded свойство"""
def __init__(self, func):
self.func = func
def __get__(self, obj, objtype=None):
if obj is None:
return self
print(f"Computing {self.func.__name__}...")
return self.func(obj)
class Person:
def __init__(self, name):
self.name = name
@Lazy
def full_info(self):
# Вычисляется только при доступе
return f"Person: {self.name}"
p = Person("Alice")
print(p.full_info) # Вычисляется один раз
print(p.full_info) # Ещё раз вычисляется (no caching)
3. Валидирующий дескриптор (data дескриптор)
class ValidatedString:
"""Дескриптор, валидирующий строку"""
def __init__(self, minsize=0, maxsize=None):
self.minsize = minsize
self.maxsize = maxsize
def __get__(self, obj, objtype=None):
if obj is None:
return self
return obj.__dict__.get(self.name, '')
def __set__(self, obj, value):
if not isinstance(value, str):
raise TypeError(f"{self.name} must be a string")
if len(value) < self.minsize:
raise ValueError(f"{self.name} is too short")
if self.maxsize and len(value) > self.maxsize:
raise ValueError(f"{self.name} is too long")
obj.__dict__[self.name] = value
def __set_name__(self, owner, name):
self.name = name # Автоматически получаем имя атрибута
class User:
username = ValidatedString(minsize=3, maxsize=20)
email = ValidatedString(minsize=5, maxsize=100)
user = User()
user.username = "alice" # OK
print(user.username) # alice
try:
user.username = "ab" # Слишком короткое
except ValueError as e:
print(e) # username is too short
4. Кеширующий дескриптор
class Cached:
"""Кеширует результат вычисления"""
def __init__(self, func):
self.func = func
self.cache = {}
def __get__(self, obj, objtype=None):
if obj is None:
return self
obj_id = id(obj)
if obj_id not in self.cache:
print(f"Computing {self.func.__name__}...")
self.cache[obj_id] = self.func(obj)
else:
print(f"Using cached {self.func.__name__}")
return self.cache[obj_id]
class Circle:
def __init__(self, radius):
self.radius = radius
@Cached
def area(self):
import math
return math.pi * self.radius ** 2
c = Circle(5)
print(c.area) # Computing area...
print(c.area) # Using cached area
5. Дескриптор для типизации
class TypedProperty:
"""Дескриптор, проверяющий тип значения"""
def __init__(self, name, expected_type):
self.name = name
self.expected_type = expected_type
def __get__(self, obj, objtype=None):
if obj is None:
return self
return obj.__dict__.get(self.name)
def __set__(self, obj, value):
if not isinstance(value, self.expected_type):
raise TypeError(
f"{self.name} must be {self.expected_type.__name__}, "
f"got {type(value).__name__}"
)
obj.__dict__[self.name] = value
class Student:
name = TypedProperty('name', str)
age = TypedProperty('age', int)
gpa = TypedProperty('gpa', float)
student = Student()
student.name = "Alice"
student.age = 20
student.gpa = 3.8
try:
student.age = "twenty" # Error
except TypeError as e:
print(e) # age must be int, got str
6. Дескриптор для логирования доступа
class Logged:
"""Логирует доступ к атрибуту"""
def __init__(self):
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} = {value}")
self.value = value
def __set_name__(self, owner, name):
self.name = name
class BankAccount:
balance = Logged()
account = BankAccount()
account.balance = 1000 # Setting balance = 1000
print(account.balance) # Getting balance
7. Дескриптор для method binding
class BoundMethod:
"""Связывает функцию с объектом"""
def __init__(self, func):
self.func = func
def __get__(self, obj, objtype=None):
if obj is None:
return self.func
# Возвращаем bound method
return lambda *args, **kwargs: self.func(obj, *args, **kwargs)
class Calculator:
@BoundMethod
def add(self, a, b):
return a + b
calc = Calculator()
print(calc.add(2, 3)) # 5
8. @property как дескриптор
# property в Python — это встроенный дескриптор
class Temperature:
def __init__(self, celsius):
self._celsius = celsius
@property # __get__
def celsius(self):
return self._celsius
@celsius.setter # __set__
def celsius(self, value):
if value < -273.15:
raise ValueError("Temperature below absolute zero")
self._celsius = value
@celsius.deleter # __delete__
def celsius(self):
del self._celsius
@property
def fahrenheit(self): # read-only
return self._celsius * 9/5 + 32
t = Temperature(25)
print(t.celsius) # 25
print(t.fahrenheit) # 77.0
t.celsius = 30 # Использует setter
print(t.celsius) # 30
9. Дескриптор с аннотациями
class Annotation:
"""Дескриптор, использующий type hints"""
def __set_name__(self, owner, name):
self.name = name
self.expected_type = owner.__annotations__.get(name)
def __get__(self, obj, objtype=None):
if obj is None:
return self
return obj.__dict__.get(self.name)
def __set__(self, obj, value):
if self.expected_type:
if not isinstance(value, self.expected_type):
raise TypeError(
f"{self.name}: expected {self.expected_type}, "
f"got {type(value)}"
)
obj.__dict__[self.name] = value
class Book:
title: str = Annotation()
author: str = Annotation()
pages: int = Annotation()
book = Book()
book.title = "The Great Gatsby"
book.author = "F. Scott Fitzgerald"
book.pages = 180
print(f"{book.title} by {book.author}")
10. Best practices
# ✓ Хорошо: Data дескриптор (с __set__)
class ValidatedEmail:
def __get__(self, obj, objtype=None):
return obj.__dict__.get('email')
def __set__(self, obj, value):
if '@' not in value:
raise ValueError("Invalid email")
obj.__dict__['email'] = value
# ✓ Хорошо: Используй __set_name__ для автоматического имени
class Descriptor:
def __set_name__(self, owner, name):
self.name = name
# ✓ Хорошо: Используй @property вместо дескриптора когда это проще
class Point:
@property
def x(self):
return self._x
# ✗ Плохо: Слишком сложный дескриптор
class OverkillDescriptor:
def __get__(self, obj, objtype=None):
# Огромная логика здесь
pass
Заключение
Дескрипторы — это мощный инструмент для контроля доступа к атрибутам. Используй их для валидации, логирования, кеширования и типизации. Но помни: в большинстве случаев @property проще и понятнее!