2016-03-16 13 views
20

A partire da Python 3.4, esiste un descrittore chiamato DynamicClassAttribute. La documentazione afferma:Che cos'è un DynamicClassAttribute e come lo uso?

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

percorso attributo di accesso su una classe di __getattr__.

Questo è un descrittore, utilizzato per definire attributi che si comportano diversamente quando si accede attraverso un'istanza e una classe. L'accesso all'istanza rimane normale, ma l'accesso a un attributo attraverso una classe verrà indirizzato al metodo __getattr__ della classe; questo viene fatto aumentando AttributeError.

Ciò consente di avere proprietà attive su un'istanza e di avere attributi virtuali sulla classe con lo stesso nome (vedere Enum per un esempio).

Novità nella versione 3.4.

è apparentemente utilizzato nel enum module:

# 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_ 

mi rendo conto che enums are a little special, ma non capisco come questo si riferisce al DynamicClassAttribute. Che cosa significa che quegli attributi sono dinamici, in che modo è diverso da una proprietà normale e in che modo utilizzare un a mio vantaggio?

risposta

12

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"

Problemi correlati