Дескрипторы
Дескриптор — объект, у которого определён хотя бы один из методов __get__, __set__, __delete__. Когда такой объект лежит в атрибуте класса, обращение к атрибуту экземпляра вызывает эти методы. На дескрипторах построены property, classmethod, staticmethod и обычные методы.
property
property превращает методы в атрибут с контролируемым доступом:
class Temperature: def __init__(self, celsius): self._celsius = celsius @property def celsius(self): return self._celsius @celsius.setter def celsius(self, value): if value < -273.15: raise ValueError("ниже абсолютного нуля") self._celsius = value @celsius.deleter def celsius(self): del self._celsius @property def fahrenheit(self): return self._celsius * 9 / 5 + 32t = Temperature(25)print(t.celsius)print(t.fahrenheit)t.celsius = 30print(t.fahrenheit)Свойство без сеттера доступно только для чтения:
try: t.fahrenheit = 100except AttributeError as e: print(e)| Элемент | Описание |
|---|---|
fget, fset, fdel, __doc__ | Функции и документация свойства |
getter(func), setter(func), deleter(func) | Копия свойства с заменённой функцией |
functools.cached_property
Вычисляет значение один раз и сохраняет его в экземпляре:
from functools import cached_propertyclass Report: def __init__(self, data): self.data = data @cached_property def total(self): print("считаем") return sum(self.data)r = Report([1, 2, 3])print(r.total)print(r.total)classmethod
Метод класса получает класс первым аргументом (cls). Часто используется для альтернативных конструкторов:
class Date: def __init__(self, year, month, day): self.year = year self.month = month self.day = day @classmethod def from_string(cls, text): y, m, d = map(int, text.split("-")) return cls(y, m, d) def __repr__(self): return f"{type(self).__name__}({self.year}, {self.month}, {self.day})"class SpecialDate(Date): passprint(Date.from_string("2026-10-26"))print(SpecialDate.from_string("2026-10-26"))Благодаря cls метод возвращает объект подкласса, а не жёстко заданного класса.
staticmethod
Статический метод не получает ни self, ни cls. Это обычная функция, помещённая в пространство имён класса:
class MathTools: @staticmethod def clamp(value, low, high): return max(low, min(value, high))print(MathTools.clamp(15, 0, 10))print(MathTools().clamp(-5, 0, 10))Используйте его, когда функция логически относится к классу, но не зависит ни от состояния, ни от самого класса. Часто проще вынести такую функцию на уровень модуля.
Сравнение
| Метод | Первый аргумент | Доступ |
|---|---|---|
| Обычный | self — экземпляр | К экземпляру и классу |
@classmethod | cls — класс | К классу |
@staticmethod | нет | Ни к чему неявно |
super
super() возвращает прокси-объект, делегирующий вызовы следующему классу в MRO:
class Base: def __init__(self, name): self.name = name def describe(self): return f"Base {self.name}"class Child(Base): def __init__(self, name, extra): super().__init__(name) self.extra = extra def describe(self): return super().describe() + f" + {self.extra}"print(Child("a", "b").describe())Множественное наследование
super() следует по MRO, поэтому в ромбовидной иерархии каждый класс вызывается ровно один раз:
class A: def hello(self): print("A")class B(A): def hello(self): print("B") super().hello()class C(A): def hello(self): print("C") super().hello()class D(B, C): def hello(self): print("D") super().hello()D().hello()print([c.__name__ for c in D.__mro__])Результат: D, B, C, A, затем MRO ['D', 'B', 'C', 'A', 'object'].
Собственные дескрипторы
class Positive: def __set_name__(self, owner, name): self.name = "_" + name def __get__(self, instance, owner): if instance is None: return self return getattr(instance, self.name) def __set__(self, instance, value): if value <= 0: raise ValueError("значение должно быть положительным") setattr(instance, self.name, value)class Product: price = Positive() quantity = Positive() def __init__(self, price, quantity): self.price = price self.quantity = quantityp = Product(10, 3)print(p.price)try: p.quantity = -1except ValueError as e: print(e)Другие вспомогательные встроенные классы
| Класс | Назначение |
|---|---|
functools.partial | Фиксация части аргументов функции |
functools.wraps | Копирование метаданных функции в обёртку |
functools.singledispatch | Перегрузка функции по типу первого аргумента |
functools.total_ordering | Генерация сравнений по __eq__ и одному из __lt__ и т. п. |
types.SimpleNamespace | Простой объект с произвольными атрибутами |
types.MappingProxyType | Неизменяемое представление словаря |
from functools import partial, total_orderingfrom types import SimpleNamespacehex_to_int = partial(int, base=16)print(hex_to_int("ff"))ns = SimpleNamespace(a=1, b=2)ns.c = 3print(ns)@total_orderingclass Version: def __init__(self, major, minor): self.key = (major, minor) def __eq__(self, other): return self.key == other.key def __lt__(self, other): return self.key < other.keyprint(Version(1, 2) <= Version(1, 3))print(Version(2, 0) > Version(1, 9))Задания
- Напишите класс
Circleсо свойствамиradius,diameterиarea, чтобы изменение диаметра меняло радиус. - Реализуйте классовые конструкторы
from_jsonиfrom_dictдля класса настроек. - Создайте дескриптор, проверяющий тип присваиваемого значения.
