Nuova versione:
ero un po 'deluso con la risposta precedente, così ho deciso di riscrivere un po':
prima avere uno sguardo a the source code of DynamicClassAttribute
e probabilmente noterete, che sembra molto molto simile al normale property
. Fatta eccezione per il __get__
-Metodo:
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)
Che cosa questo significa è che se si desidera accedere a un DynamicClassAttribute
(che non è astratta) sulla classe solleva un AttributeError
invece di ritornare self
. Per le istanze if instance:
viene valutata su True
e __get__
è identica a property.__get__
.
Per le classi normali che risolve solo in un visibileAttributeError
quando si chiama l'attributo:
from types import DynamicClassAttribute
class Fun():
@DynamicClassAttribute
def has_fun(self):
return False
Fun.has_fun
AttributeError - Traceback (chiamata più recente scorso)
che per sé non è molto utile fino a dare un'occhiata alla procedura "Ricerca attributi di classe" quando usando metaclass
es (I trovato una bella immagine di questo in this blog). Perché nel caso in cui un attributo sollevi un AttributeError
e quella classe ha un metaclass python guarda il metodo metaclass.__getattr__
e vede se questo può risolvere l'attributo.Per illustrare questo con un esempio minimo:
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
E qui arriva la parte "dinamica". Se si chiama il dynprop
sulla classe cercherà nella meta e ritorno del meta dynprop
:
Fun.dynprop
che stampa:
search in meta
'Meta'
Così abbiamo invocato il metaclass.__getattr__
e restituito l'attributo originale (che era definito con lo stesso nome della nuova proprietà).
Mentre per i casi la dynprop
del Fun
-instance viene restituito:
Fun('Not-Meta').dynprop
otteniamo l'attributo sovresposta:
'Not-Meta'
mia conclusione da questo è, che DynamicClassAttribute
è importante se si vuole per consentire alle sottoclassi di avere un attributo con lo stesso nome usato nella metaclasse. Lo ombreggi sulle istanze ma è ancora accessibile se lo chiami sulla classe.
sono andato sul comportamento del Enum
nella vecchia versione in modo ho lasciato qui:
vecchia versione
Il DynamicClassAttribute
è solo utile (io non sono davvero sicuro su questo punto), se sospetti che ci possano essere conflitti di denominazione tra un attributo impostato su una sottoclasse e una proprietà sulla classe base.
Avrai bisogno di conoscere almeno alcuni principi fondamentali circa metaclassi, perché questo non funziona senza l'utilizzo di metaclassi (una bella spiegazione su come gli attributi di classe sono chiamate possono essere trovati in this blog post) perché la ricerca attributo è leggermente diversa con metaclassi.
Supponiamo di avere:
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)
e quindi chiamare:
Fun.dynprop
struttura al 0x1b3d9fd19a8
e sull'istanza otteniamo:
Fun(2).dynprop
'Haha, sovrascritti con 2'
male ... è perso. Ma aspettiamo che possiamo usare la ricerca speciale metaclass
: Implementiamo uno __getattr__
(fallback) e implementiamo lo dynprop
come DynamicClassAttribute
.Perché, secondo è documentation questo è il suo scopo - di ripiego al __getattr__
se si chiama sulla classe:
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
ora abbiamo accedere alla classe-attributi:
Fun.dynprop
che stampa:
search in meta
'Meta'
Così abbiamo invocato lo metaclass.__getattr__
e restituito l'attributo originale (che era definito con lo stesso nome della nuova proprietà).
E per le istanze:
Fun('Not-Meta').dynprop
otteniamo l'attributo sovresposta:
'Not-Meta'
Bene che non è troppo male considerando che possiamo reindirizzare utilizzando metaclassi agli attributi definiti in precedenza, ma sovrascritto senza creare un'istanza. Questo esempio è l'opposto che si fa con Enum
, in cui si definiscono gli attributi della sottoclasse:
from enum import Enum
class Fun(Enum):
name = 'me'
age = 28
hair = 'brown'
e utile per accedere alle attributi seguito definiti per impostazione predefinita.
Fun.name
# <Fun.name: 'me'>
ma anche voi desidera consentire l'accesso all'attributo name
che è stato definito come DynamicClassAttribute
(che restituisce il cui nome la variabile ha in realtà):
Fun('me').name
# 'name'
perché altrimenti come si potrebbe accedere al nome di 28
?
Fun.hair.age
# <Fun.age: 28>
# BUT:
Fun.hair.name
# returns 'hair'
Vedere la differenza? Perché il secondo non restituisce <Fun.name: 'me'>
? Questo a causa di questo uso di DynamicClassAttribute
. Quindi puoi ombreggiare la proprietà originale ma "rilasciarlo" più tardi. Questo comportamento è il contrario di quello mostrato nel mio esempio e richiede almeno l'utilizzo di __new__
e __prepare__
. Ma per questo è necessario sapere come funziona esattamente e viene spiegato in molti blog e risposte StackOverflow che possono spiegarlo molto meglio di così posso saltare in profondità (e non sono sicuro se Potrei risolverlo in breve tempo).
effettivi casi d'uso potrebbe essere scarsa ma dato momento si può pensare a qualche propably ...
Molto bella discussione sulla documentazione di DynamicClassAttribute
: "we added it because we needed it"