HOWTO по дескрипторам¶
Автор: | Raymond Hettinger |
---|---|
Контакт: | <python at rcn dot com> |
Содержание
Аннотация¶
Определяет дескрипторы, суммирует протокол и показывает, как дескрипторы вызываются. Исследует пользовательский дескриптор и несколько встроенных Python дескрипторов включая функции, свойства, статические методы и методы класса. Показывая, как каждый работает, давая чистый эквивалент Python и пример приложения.
Изучение о дескрипторы не только предоставляет доступ к большему набору инструментов, он создает более глубокое понимание того, как работает Python, и признательность за элегантность его дизайна.
Определение и введение¶
В общем случае, дескриптор - это объект атрибут с «биндинг поведением»,
чей доступ к атрибут был переопределен методы в протоколе дескриптор.
Эти методы - __get__()
, __set__()
и __delete__()
. Если какой-либо из этих
методы определен для объекта, он считается дескриптор.
Поведение по умолчанию для доступа атрибут должно получить, установить или
удалить как атрибут из словаря объекта. Например, у a.x
есть цепь
поиска, начинающаяся с a.__dict__['x']
, тогда type(a).__dict__['x']
, и продолжающаяся через
основной классы type(a)
, исключая метаклассы. Если просмотренное
значение является объектом, определяющим один из методов дескриптор, то
Python может переопределить поведение по умолчанию и вместо этого вызвать
метод дескриптор метод. Где это происходит в цепочке приоритетов, зависит
от того, какие дескриптор методы были определены.
Дескрипторы являются мощным протоколом общего назначения. Они являются
механизмом, лежащим в основе свойств, методов, статических методов, методов
класс и super()
. Они используемый по всей самой Python для
реализации нового стиля классы, представленного в версии 2.2. Дескрипторы
упростить базовые C-код и предложить гибкий набор новых инструментов для
повседневных программ Python.
Протокол дескриптора¶
descr.__get__(self, obj, type=None) -> value
descr.__set__(self, obj, value) -> None
descr.__delete__(self, obj) -> None
Вот и все, что есть. Определите любую из этих методы, и объект считается дескриптор и может переопределить поведение по умолчанию при просмотре в качестве атрибут.
Если объект определяет __set__()
или __delete__()
, он считается дескриптор
данных. Дескрипторы, которые только определяют __get__()
, называют неданными
дескрипторы (они, как правило - используемый для методы, но другие применения
возможны).
Данные и не-данные дескрипторы отличаются тем, как вычисляются переопределения относительно статей в словаре экземпляра. Если словарь экземпляра имеет статью с тем же именем, что и дескриптор данных, то приоритет имеет дескриптор данных. Если словарь экземпляра имеет статью с тем же именем, что и дескриптор, не являющийся дескриптором данных, то словарная статья имеет приоритет.
Чтобы создать дескриптор данных, доступный только для чтения, определите как
__get__()
, так и __set__()
с помощью __set__()
, поднимающего AttributeError
при вызове. Определения __set__()
метод с местозаполнителем повышения
исключения достаточно, чтобы сделать его дескриптор данных.
Вызов дескрипторов¶
дескриптор может вызываться непосредственно по имени метода. Например,
d.__get__(obj)
.
Альтернативно, для дескриптор более часто вызывается автоматически при доступе
к атрибут. Например, obj.d
ищет d
в словаре obj
. Если
d
определяет метод __get__()
, то d.__get__(obj)
вызывается согласно
правилам приоритета, перечисленным ниже.
Подробности вызова зависят от того, является ли obj
объектом или
класс.
Для объектов механизм находится в object.__getattribute__()
, который преобразует
b.x
в type(b).__dict__['x'].__get__(b, type(b))
. Реализация работает через
цепочку приоритетов, которая обеспечивает приоритет данных дескрипторы над переменными
сущность, приоритет переменных сущность над дескрипторами, не являющимися
данными и присваивает наименьший приоритет __getattr__()
, если они предоставляются.
Полное внедрение C может быть найдено in:c:func:PyObject_GenericGetAttr() в Objects/object.c.
Для классов механизм находится в type.__getattribute__()
, который преобразует B.x
в
B.__dict__['x'].__get__(None, B)
. В чистом питоне это выглядит как:
def __getattribute__(self, key):
"Emulate type_getattro() in Objects/typeobject.c"
v = object.__getattribute__(self, key)
if hasattr(v, '__get__'):
return v.__get__(None, self)
return v
Важно помнить следующее:
- дескрипторы призваны
__getattribute__()
метод - отвергающим
__getattribute__()
, предотвращает автоматические требования дескриптор object.__getattribute__()
иtype.__getattribute__()
делают различные звонки__get__()
.- данные дескрипторы всегда переопределяют словари сущность.
- non-data дескрипторы может быть переопределен сущность словарями.
Объект, возвращенный super()
, также имеет настраиваемый __getattribute__()
метод для вызова дескрипторы. Поиск атрибут super(B, obj).m
ищет
obj.__class__.__mro__
основной класс A
сразу после B
и затем
возвращает A.__dict__['m'].__get__(obj, B)
. Если дескриптор отсутствует, m
возвращается
без изменений. Если нет в словаре, m
возвращается к поиску с помощью
object.__getattribute__()
.
Детали внедрения в super_getattro()
в Objects/typeobject.c. и чистый эквивалент Python
может быть найден в Учебник Гвидо.
Приведенные выше подробности показывают, что механизм дескрипторы встроен в
__getattribute__()
методы для object
, type
и super()
. Классы
наследуют этот механизм, когда они происходят от object
или если у них есть
meta-класс, обеспечивающие аналогичную функциональность. Аналогично,
классы может отключать вызов дескриптор путем переопределения __getattribute__()
.
Пример дескриптор¶
Следующий код создает класс, объектами которых являются данные
дескрипторы, которые печатают сообщение для каждого получения или набора.
Переопределение __getattribute__()
- это альтернативный подход, который может сделать
это для каждого атрибут. Однако этот дескриптор полезен для контроля всего
нескольких выбранных признаков:
class RevealAccess(object):
"""A data descriptor that sets and returns values
normally and prints a message logging their access.
"""
def __init__(self, initval=None, name='var'):
self.val = initval
self.name = name
def __get__(self, obj, objtype):
print('Retrieving', self.name)
return self.val
def __set__(self, obj, val):
print('Updating', self.name)
self.val = val
>>> class MyClass(object):
... x = RevealAccess(10, 'var "x"')
... y = 5
...
>>> m = MyClass()
>>> m.x
Retrieving var "x"
10
>>> m.x = 20
Updating var "x"
>>> m.x
Retrieving var "x"
20
>>> m.y
5
Протокол прост и предлагает захватывающие возможности. Несколько вариантов использования являются настолько распространенными, что они были упакованы в отдельные вызовы функций. Свойства, связанные методы, статические методы и класс методы основаны на протоколе дескриптор.
Свойства¶
Вызов property()
является кратким способом построения дескриптор данных,
который запускает вызовы функции при доступе к атрибут. Его сигнатура
property(fget=None, fset=None, fdel=None, doc=None) -> property attribute
В документации показано типичное использование для определения управляемых
атрибут x
:
class C(object):
def getx(self): return self.__x
def setx(self, value): self.__x = value
def delx(self): del self.__x
x = property(getx, setx, delx, "I'm the 'x' property.")
Чтобы увидеть, как property()
реализуется с точки зрения протокола дескриптор,
вот чистый эквивалент Python:
class Property(object):
"Emulate PyProperty_Type() in Objects/descrobject.c"
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget(obj)
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(obj, value)
def __delete__(self, obj):
if self.fdel is None:
raise AttributeError("can't delete attribute")
self.fdel(obj)
def getter(self, fget):
return type(self)(fget, self.fset, self.fdel, self.__doc__)
def setter(self, fset):
return type(self)(self.fget, fset, self.fdel, self.__doc__)
def deleter(self, fdel):
return type(self)(self.fget, self.fset, fdel, self.__doc__)
Встроенное property()
помогает каждый раз, когда пользовательский интерфейс
предоставил доступ атрибут, и затем последующие изменения требуют
вмешательства метод.
Например, электронная таблица класс может предоставлять доступ к значению
ячейки через Cell('b10').value
. Последующие усовершенствования программы требуют
перерасчета ячейки при каждом доступе; однако программист не хочет
воздействовать на существующие клиентские код, непосредственно обращаясь
к атрибут. Решением является перенос доступа к значению атрибут в
дескрипторе данных свойства:
class Cell(object):
. . .
def getvalue(self):
"Recalculate the cell before returning value"
self.recalc()
return self._value
value = property(getvalue)
Функции и методы¶
Python’s объектно-ориентированные функции строятся на основе функциональной среды. Используя дескрипторы, не связанные с данными, эти два элемента легко объединяются.
Словари классов хранят методы как функции. В определении класс
методы записываются с использованием def
или lambda
, обычных
инструментов для создания функций. Методы отличаются от обычных функций только
тем, что первый аргумент зарезервирован для объекта сущность. В соответствии с
конвенцией Python, ссылку сущность называют self, но можно назвать
this или любым другим именем переменной.
Для поддержки вызовов метод функции включают __get__()
метод для
биндинг методы во время доступа к атрибут. Это означает, что все
функции не являются дескрипторы данных, которые возвращают привязку методы
при вызове из объекта. В чистом питоне это работает так:
class Function(object):
. . .
def __get__(self, obj, objtype=None):
"Simulate func_descr_get() in Objects/funcobject.c"
if obj is None:
return self
return types.MethodType(self, obj)
Управление интерпретатор показывает, как функция дескриптор работает на практике:
>>> class D(object):
... def f(self, x):
... return x
...
>>> d = D()
# Доступ через словарь классов не вызывает __get__.
# Он просто возвращает базовый объект функции.
>>> D.__dict__['f']
<function D.f at 0x00C45070>
# Доступ через словарь классов не вызывает __get__.
# the underlying function unchanged.
>>> D.f
<function D.f at 0x00C45070>
# Функция имеет атрибут __qualname__ для поддержки самоанализа
>>> D.f.__qualname__
'D.f'
# Точечный доступ из экземпляра вызывает __get __(), который возвращает функцию,
# заключенную в связанный объект метода
>>> d.f
<bound method D.f of <__main__.D object at 0x00B18C90>>
# Внутри связанный метод хранит базовую функцию и
# связанный экземпляр.
>>> d.f.__func__
<function D.f at 0x1012e5ae8>
>>> d.f.__self__
<__main__.D object at 0x1012e1f98>
Статические методы и методы классов¶
Неданные дескрипторы обеспечивают простой механизм для вариаций на обычные узоры функций биндинг в методы.
Для повторного отображения функции имеют __get__()
метод, чтобы их можно
было преобразовать в метод при обращении как атрибуты. Неданные
дескриптор преобразуют вызов obj.f(*args)
в f(obj, *args)
. Вызов klass.f(*args)
становится f(*args)
.
На этой диаграмме обобщены биндинг и два наиболее полезных варианта:
Преобразование Вызывается из объекта Вызывается из класса function f(obj, *args) f(*args) staticmethod f(*args) f(*args) classmethod f(type(obj), *args) f(klass, *args)
Статические методы возвращают основную функцию без изменений. Вызов
c.f
или C.f
является эквивалентом прямого поиска в object.__getattribute__(c, "f")
или
object.__getattribute__(C, "f")
. В результате функция становится тождественно доступной или от
объекта или от класс.
Хорошие кандидаты для статических методы являются методы, которые не
ссылаются на переменную self
.
Например, пакет статистики может включать в себя контейнер класс для
экспериментальных данных. класс обеспечивает нормальный методы для
вычислений среднего числа, средние, средние, и другие описательные
статистические данные, которые зависят от данных. Однако могут быть полезные
функции, которые концептуально связаны, но не зависят от данных. Например,
erf(x)
- это удобная подпрограмма преобразования, которая появляется в
статистической работе, но не напрямую зависит от конкретного набора данных. Его
можно вызвать либо из объекта, либо из класса: s.erf(1.5) --> .9332
или Sample.erf(1.5) --> .9332
.
Так как статические методы возвращают базовую функцию без изменений, вызовы примера являются нескончаемыми:
>>> class E(object):
... def f(x):
... print(x)
... f = staticmethod(f)
...
>>> E.f(3)
3
>>> E().f(3)
3
Используя неданные протокол дескриптор, чистая версия Python staticmethod()
была бы похожа на это:
class StaticMethod(object):
"Emulate PyStaticMethod_Type() in Objects/funcobject.c"
def __init__(self, f):
self.f = f
def __get__(self, obj, objtype=None):
return self.f
В отличие от статических методов, класс методы перед вызовом функции добавить ссылку класс к списку аргументов. Этот формат одинаков для того, является ли вызывающий объект объектом или классом:
>>> class E(object):
... def f(klass, x):
... return klass.__name__, x
... f = classmethod(f)
...
>>> print(E.f(3))
('E', 3)
>>> print(E().f(3))
('E', 3)
Такое поведение полезно всякий раз, когда функция должна иметь только ссылку на
класс и не заботится о базовых данных. Одним из способов использования
классовых методов является создание альтернативных конструкторов класс. В
Python 2.3 классметод dict.fromkeys()
создает новый словарь из списка ключей.
Чистый эквивалент Python:
class Dict(object):
. . .
def fromkeys(klass, iterable, value=None):
"Emulate dict_fromkeys() in Objects/dictobject.c"
d = klass()
for key in iterable:
d[key] = value
return d
fromkeys = classmethod(fromkeys)
Теперь новый словарь уникальных ключей может быть построен так:
>>> Dict.fromkeys('abracadabra')
{'a': None, 'r': None, 'b': None, 'c': None, 'd': None}
Используя неданные протокол дескриптор, чистая версия Python classmethod()
была бы похожа на это:
class ClassMethod(object):
"Emulate PyClassMethod_Type() in Objects/funcobject.c"
def __init__(self, f):
self.f = f
def __get__(self, obj, klass=None):
if klass is None:
klass = type(obj)
def newfunc(*args):
return self.f(klass, *args)
return newfunc