python - ¿Qué es un DynamicClassAttribute y cómo lo uso?
python-3.4 (1)
A partir de Python 3.4, hay un descriptor llamado DynamicClassAttribute
. La documentación establece:
types.DynamicClassAttribute(fget=None, fset=None, fdel=None, doc=None)
Ruta de acceso de atributo en una clase a
__getattr__
.Este es un descriptor, usado para definir atributos que actúan de manera diferente cuando se accede a través de una instancia y a través de una clase. El acceso a la instancia sigue siendo normal, pero el acceso a un atributo a través de una clase se enrutará al método
__getattr__
la clase; esto se hace elevandoAttributeError
.Esto permite tener propiedades activas en una instancia y tener atributos virtuales en la clase con el mismo nombre (ver Enum para un ejemplo).
Nuevo en la versión 3.4.
Aparentemente se usa en el módulo de enumeración :
# 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_
Me doy cuenta de que las enumeraciones son un poco especiales , pero no entiendo cómo se relaciona esto con el DynamicClassAttribute
. ¿Qué significa que esos atributos son dinámicos , en qué se diferencia esto de una propiedad normal y cómo uso un DynamicClassAttribute
para mi ventaja?
Nueva versión:
Me decepcionó un poco la respuesta anterior, así que decidí reescribirla un poco:
Primero, eche un vistazo al código fuente de DynamicClassAttribute
y probablemente se dará cuenta de que se parece mucho a la property
normal. Excepto por el método __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)
Entonces, lo que esto significa es que si desea acceder a un DynamicClassAttribute
(que no es abstracto) en la clase, genera un AttributeError
lugar de devolverse a self
. Para instancias if instance:
evalúa a True
y el __get__
es idéntico a la property.__get__
.
Para las clases normales que solo se resuelven en un AttributeError
visible al llamar al atributo:
from types import DynamicClassAttribute
class Fun():
@DynamicClassAttribute
def has_fun(self):
return False
Fun.has_fun
AttributeError - Seguimiento (última llamada más reciente)
eso por sí solo no es muy útil hasta que se analiza el procedimiento de "Búsqueda de atributos de clase" cuando se usan metaclass
(encontré una buena imagen de esto en este blog ). Porque en caso de que un atributo genere un AttributeError
y esa clase tenga una metaclase, python mira el método metaclass.__getattr__
y ve si eso puede resolver el atributo. Para ilustrar esto con un ejemplo mínimo:
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
Y aquí viene la parte "dinámica". Si llama al dynprop
en la clase, buscará en el meta y devolverá el dynprop
del meta:
Fun.dynprop
que imprime:
search in meta
''Meta''
Así que metaclass.__getattr__
la metaclass.__getattr__
y metaclass.__getattr__
el atributo original (que se definió con el mismo nombre que la nueva propiedad).
Mientras que, por ejemplo, se dynprop
el dynprop
de la instancia Fun
:
Fun(''Not-Meta'').dynprop
obtenemos el atributo anulado:
''Not-Meta''
Mi conclusión de esto es que DynamicClassAttribute
es importante si desea permitir que las subclases tengan un atributo con el mismo nombre que se usa en la metaclase. Lo ocultará en las instancias, pero sigue siendo accesible si lo llama en la clase.
Entré en el comportamiento de Enum
en la versión anterior, así que lo dejé aquí:
Versión antigua
El DynamicClassAttribute
es simplemente útil (no estoy seguro en ese punto) si sospecha que podría haber conflictos de nombres entre un atributo que se establece en una subclase y una propiedad en la clase base.
Necesitará conocer al menos algunos aspectos básicos sobre las metaclases, ya que esto no funcionará sin usar metaclases (en esta publicación del blog se puede encontrar una buena explicación de cómo se llaman los atributos de clase) porque la búsqueda de atributos es ligeramente diferente con las metaclases.
Supongamos que usted tiene:
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)
y luego llamar:
Fun.dynprop
propiedad en 0x1b3d9fd19a8
y en la instancia tenemos:
Fun(2).dynprop
''Jaja, anulado con 2''
mal ... se pierde Pero espera, podemos usar la búsqueda especial de metaclass
: Implementemos un __getattr__
(reserva) e implementemos el dynprop
como DynamicClassAttribute
. Porque según su DynamicClassAttribute ese es su propósito: __getattr__
al __getattr__
si se llama en la clase:
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
Ahora accedemos al atributo de clase:
Fun.dynprop
que imprime:
search in meta
''Meta''
Así que metaclass.__getattr__
la metaclass.__getattr__
y metaclass.__getattr__
el atributo original (que se definió con el mismo nombre que la nueva propiedad).
Y por ejemplo:
Fun(''Not-Meta'').dynprop
obtenemos el atributo anulado:
''Not-Meta''
Bueno, eso no es tan malo teniendo en cuenta que podemos redirigir el uso de metaclases a los atributos previamente definidos pero anulados sin crear una instancia. Este ejemplo es lo contrario que se hace con Enum
, donde se definen atributos en la subclase:
from enum import Enum
class Fun(Enum):
name = ''me''
age = 28
hair = ''brown''
y desea acceder a estos atributos definidos posteriormente de forma predeterminada.
Fun.name
# <Fun.name: ''me''>
pero también desea permitir el acceso al atributo de name
que se definió como DynamicClassAttribute
(que devuelve el nombre que realmente tiene la variable):
Fun(''me'').name
# ''name''
porque de lo contrario ¿cómo podrías acceder al nombre de 28
?
Fun.hair.age
# <Fun.age: 28>
# BUT:
Fun.hair.name
# returns ''hair''
¿Ver la diferencia? ¿Por qué el segundo no devuelve <Fun.name: ''me''>
? Eso es debido a este uso de DynamicClassAttribute
. Así que puedes sombrear la propiedad original pero "liberarla" más tarde. Este comportamiento es el contrario al que se muestra en mi ejemplo y requiere al menos el uso de __new__
y __prepare__
. Pero para eso necesitas saber cómo funciona exactamente y se explica en muchos blogs y en -respuestas que pueden explicarlo mucho mejor de lo que yo puedo, así que me saltaré esa profundidad (y no estoy seguro si Podría resolverlo en poco tiempo).
Los casos de uso reales pueden ser escasos, pero con el tiempo se puede pensar en algunos ...
Muy buena discusión sobre la documentación de DynamicClassAttribute
: "lo agregamos porque lo necesitábamos"