python class python-3.4

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 elevando AttributeError .

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"