Ответ 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
: "мы добавили его, потому что нам это нужно"