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
