Подтвердить что ты не робот

Что такое DynamicClassAttribute и как его использовать?

С Python 3.4 существует дескриптор DynamicClassAttribute. В документации указано:

types.DynamicClassAttribute(fget=None, fset=None, fdel=None, doc=None)

Доступ атрибута маршрута в классе к __getattr__.

Это дескриптор, используемый для определения атрибутов, которые действуют по-разному при доступе через экземпляр и через класс. Доступ к экземпляру остается нормальным, но доступ к атрибуту через класс будет перенаправлен на метод классов __getattr__; это делается путем повышения AttributeError.

Это позволяет иметь активные свойства в экземпляре и иметь виртуальные атрибуты для класса с тем же именем (см. Enum для примера).

Новое в версии 3.4.

Он, по-видимому, используется в перечислении модуля:

# DynamicClassAttribute is used to provide access to the `name` and
# `value` properties of enum members while keeping some measure of
# protection from modification, while still allowing for an enumeration
# to have members named `name` and `value`.  This works because enumeration
# members are not set directly on the enum class -- __getattr__ is
# used to look them up.

@DynamicClassAttribute
def name(self):
    """The name of the Enum member."""
    return self._name_

@DynamicClassAttribute
def value(self):
    """The value of the Enum member."""
    return self._value_

Я понимаю, что перечисления немного особенные, но я не понимаю, как это относится к DynamicClassAttribute. Что означает, что эти атрибуты являются динамическими, как это отличается от обычного свойства, и как использовать DynamicClassAttribute в моих интересах?

4b9b3361

Ответ 1

Новая версия:

Я был немного разочарован предыдущим ответом, поэтому решил немного переписать его:

Сначала посмотрите исходный код DynamicClassAttribute, и вы, вероятно, заметите, что он очень похож на обычный property. За исключением метода __get__:

def __get__(self, instance, ownerclass=None):
    if instance is None:
        # Here is the difference, the normal property just does: return self
        if self.__isabstractmethod__:
            return self
        raise AttributeError()
    elif self.fget is None:
        raise AttributeError("unreadable attribute")
    return self.fget(instance)

Итак, это означает, что если вы хотите получить доступ к DynamicClassAttribute (который не является абстрактным) в классе, он вызывает AttributeError вместо возврата self. Для экземпляров if instance: оценивается True, а __get__ идентично property.__get__.

Для обычных классов, которые просто разрешаются в видимом AttributeError при вызове атрибута:

from types import DynamicClassAttribute
class Fun():
    @DynamicClassAttribute
    def has_fun(self):
        return False
Fun.has_fun

AttributeError - Traceback (последний последний вызов)

что для себя не очень полезно, пока вы не посмотрите на процедуру поиска атрибутов класса , когда с помощью metaclass es (я нашел приятное изображение этого в этот блог). Поскольку в случае, если атрибут вызывает AttributeError, и этот класс имеет метаклассовый python, он смотрит на метод metaclass.__getattr__ и видит, сможет ли этот атрибут разрешить этот атрибут. Чтобы проиллюстрировать это с минимальным примером:

from types import DynamicClassAttribute

# Metaclass
class Funny(type):

    def __getattr__(self, value):
        print('search in meta')
        # Normally you would implement here some ifs/elifs or a lookup in a dictionary
        # but I'll just return the attribute
        return Funny.dynprop

    # Metaclasses dynprop:
    dynprop = 'Meta'

class Fun(metaclass=Funny):
    def __init__(self, value):
        self._dynprop = value

    @DynamicClassAttribute
    def dynprop(self):
        return self._dynprop

И вот идет "динамическая" часть. Если вы вызываете dynprop в классе, он будет искать в мета и возвращать meta dynprop:

Fun.dynprop

который печатает:

search in meta
'Meta'

Итак, мы вызвали metaclass.__getattr__ и вернули исходный атрибут (который был определен с тем же именем, что и новое свойство).

Пока для экземпляров возвращается dynprop из Fun -instance:

Fun('Not-Meta').dynprop

мы получаем атрибут overriden:

'Not-Meta'

Мое заключение состоит в том, что DynamicClassAttribute важно, если вы хотите разрешить подклассам иметь атрибут с тем же именем, что и в метаклассе. Вы будете затенять его на экземплярах, но он все еще доступен, если вы вызываете его в классе.

В старой версии я ввел поведение Enum, поэтому я оставил его здесь:

Старая версия

DynamicClassAttribute просто полезен (я не уверен в этом), если вы подозреваете, что могут быть именования конфликтов между атрибутом, установленным в подклассе, и свойством базового класса.

Вам нужно знать хотя бы некоторые основы метаклассов, потому что это не сработает без использования метаклассов (хорошее объяснение того, как называются атрибуты класса, можно найти в этом блоге post), потому что поиск атрибута немного отличается от метаклассов.

Предположим, что у вас есть:

class Funny(type):
    dynprop = 'Very important meta attribute, do not override'

class Fun(metaclass=Funny):
    def __init__(self, value):
        self._stub = value

    @property
    def dynprop(self):
        return 'Haha, overridden it with {}'.format(self._stub)

а затем вызовите:

Fun.dynprop

свойство в 0x1b3d9fd19a8

и на экземпляре получаем:

Fun(2).dynprop

'Ха-ха, переопределил его 2'

плохой... он проиграл. Но подождите, мы сможем использовать специальный поиск metaclass: пусть реализуем __getattr__ (резервный) и реализуем dynprop как DynamicClassAttribute. Потому что согласно документации его цель - отказать в __getattr__, если он вызвал класс:

from types import DynamicClassAttribute

class Funny(type):
    def __getattr__(self, value):
        print('search in meta')
        return Funny.dynprop

    dynprop = 'Meta'

class Fun(metaclass=Funny):
    def __init__(self, value):
        self._dynprop = value

    @DynamicClassAttribute
    def dynprop(self):
        return self._dynprop

теперь мы получаем доступ к атрибуту class:

Fun.dynprop

который печатает:

search in meta
'Meta'

Итак, мы вызвали metaclass.__getattr__ и вернули исходный атрибут (который был определен с тем же именем, что и новое свойство).

И для экземпляров:

Fun('Not-Meta').dynprop

мы получаем атрибут overriden:

'Not-Meta'

Хорошо, что не так уж плохо, потому что мы можем перенаправить использование метаклассов в ранее определенные, но переопределенные атрибуты без создания экземпляра. Этот пример противоположный, который выполняется с помощью Enum, где вы определяете атрибуты в подклассе:

from enum import Enum

class Fun(Enum):
    name = 'me'
    age = 28
    hair = 'brown'

и хотите получить доступ к этим атрибутам после этого по умолчанию.

Fun.name
# <Fun.name: 'me'>

но вы также хотите разрешить доступ к атрибуту name, который был определен как DynamicClassAttribute (который возвращает имя, которое фактически имеет переменная):

Fun('me').name
# 'name'

иначе как вы могли бы получить доступ к имени 28?

Fun.hair.age
# <Fun.age: 28>
# BUT:
Fun.hair.name
# returns 'hair'

Посмотрите разницу? Почему второй не возвращает <Fun.name: 'me'>? Это из-за этого использования DynamicClassAttribute. Таким образом, вы можете затенять исходное свойство, но "освободить" его позже. Это поведение обратное тому, что показано в моем примере, и требует, по крайней мере, использования __new__ и __prepare__. Но для этого вам нужно знать, как это работает, и объясняется во многих блогах и stackoverflow-ответах, которые могут объяснить это намного лучше, чем я могу, поэтому я пропущу эту глубину (и я не уверен, что Я мог бы решить это в короткий срок).

Фактические варианты использования могут быть разреженными, но с учетом времени, которое можно с уверенностью подумать о некоторых...

Очень приятное обсуждение документации DynamicClassAttribute: "мы добавили его, потому что нам это нужно"

Ответ 2

Что такое DynamicClassAttribute

DynamicClassAttribute - это дескриптор, похожий на property. Dynamic является частью имени, потому что вы получаете разные результаты в зависимости от того, обращаетесь ли вы к нему через класс или через экземпляр:

  • Доступ к экземпляру идентичен property и просто запускает любой метод, оформленный, возвращая его результат.

  • доступ к классу вызывает AttributeError; когда это происходит, Python затем ищет каждый родительский класс (через mro) в поисках этого атрибута - когда он не находит его, он вызывает класс ' __getattr__ ', чтобы последний раз найти атрибут. __getattr__ может, конечно, делать все, что захочет - в случае EnumMeta __getattr__ просматривает класс ' _member_map_ чтобы увидеть, есть ли запрошенный атрибут, и возвращает его, если он есть. В качестве примечания: весь этот поиск оказал серьезное влияние на производительность, поэтому мы в конечном итоге поместили всех членов, у которых не было конфликтов имен, с DynamicClassAttribute в класс Enum ' __dict__ конце концов.

и как мне это использовать?

Вы используете его так же, как и property - единственное отличие состоит в том, что вы используете его при создании базового класса для других перечислений. Например, Enum из aenum 1 имеет три зарезервированных имени:

  • name
  • value
  • values

values для поддержки членов Enum с несколькими значениями. Этот класс эффективно:

class Enum(metaclass=EnumMeta):

    @DynamicClassAttribute
    def name(self):
        return self._name_

    @DynamicClassAttribute
    def value(self):
        return self._value_

    @DynamicClassAttribute
    def values(self):
        return self._values_

и теперь любой aenum.Enum может иметь values member, не путая Enum.<member>.values.


1 Раскрытие информации: я являюсь автором Python stdlib Enum, enum34 и библиотеки Advanced Enumeration (aenum).