plots - python set axis label



Cos'è un DynamicClassAttribute e come lo utilizzo? (1)

A partire da Python 3.4, esiste un descrittore chiamato DynamicClassAttribute . La documentazione afferma:

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

Instrada l'accesso agli attributi di una classe su __getattr__ .

Questo è un descrittore, utilizzato per definire attributi che agiscono in modo diverso quando si accede attraverso un'istanza e attraverso una classe. L'accesso __getattr__ rimane normale, ma l'accesso a un attributo attraverso una classe verrà instradato al metodo __getattr__ della classe; questo viene fatto sollevando 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 è usato nel modulo enum :

# 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 le enumerazioni sono un po 'speciali , ma non capisco come ciò si riferisca al DynamicClassAttribute . Che cosa significa che quegli attributi sono dinamici , in che modo è diverso da una proprietà normale e in che modo posso utilizzare DynamicClassAttribute a mio vantaggio?


Answer #1

Nuova versione:

Sono rimasto un po 'deluso dalla risposta precedente, quindi ho deciso di riscriverlo un po':

Per prima cosa dai un'occhiata al codice sorgente di DynamicClassAttribute e probabilmente noterai che assomiglia molto alla normale property . Tranne il __get__ __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)

Quindi, questo significa che se vuoi accedere a un DynamicClassAttribute (che non è astratto) sulla classe solleva un AttributeError invece di restituire self . Per istanze if instance: __get__ True e __get__ è identica alla property.__get__ .

Per le classi normali che si risolvono in un AttributeError visibile quando si chiama l'attributo:

from types import DynamicClassAttribute
class Fun():
    @DynamicClassAttribute
    def has_fun(self):
        return False
Fun.has_fun

AttributeError - Traceback (ultima chiamata più recente)

questo per sé non è molto utile fino a quando non si dà un'occhiata alla procedura di "Attribuzione attributi di classe" quando si utilizza la metaclass es (ho trovato una bella immagine di questo in questo 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 chiami il dynprop sulla classe, esso cercherà nel meta e restituirà il dynprop del meta:

Fun.dynprop

che stampa:

search in meta
'Meta'

Quindi abbiamo invocato il metaclass.__getattr__ e restituito l'attributo originale (che era definito con lo stesso nome della nuova proprietà).

Mentre per le istanze viene restituito il dynprop di Fun -instance:

Fun('Not-Meta').dynprop

otteniamo l'attributo overriden:

'Not-Meta'

La mia conclusione è che DynamicClassAttribute è importante se si desidera consentire alle sottoclassi di avere un attributo con lo stesso nome utilizzato nel metaclass. Lo ombreggi sulle istanze ma è ancora accessibile se lo chiami sulla classe.

Ho fatto il comportamento di Enum nella vecchia versione, quindi l'ho lasciato qui:

Vecchia versione

Il DynamicClassAttribute è utile (non sono proprio sicuro su questo punto) se si sospetta che possano esistere conflitti di denominazione tra un attributo impostato su una sottoclasse e una proprietà sulla classe base.

Avrai bisogno di conoscere almeno alcune nozioni di base sui metaclassi, perché questo non funzionerà senza l'utilizzo di metaclassi (una buona spiegazione su come gli attributi di classe sono chiamati in questo post del blog ) perché la ricerca degli attributi è leggermente diversa con le 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 chiama:

Fun.dynprop

proprietà a 0x1b3d9fd19a8

e sull'istanza otteniamo:

Fun(2).dynprop

'Haha, sovrascritto con 2'

male ... è perso. Ma aspettiamo che possiamo usare la ricerca speciale metaclass : implementiamo __getattr__ (fallback) e implementiamo dynprop come DynamicClassAttribute . Perché secondo la sua DynamicClassAttribute il suo scopo - __getattr__ al __getattr__ se viene chiamato 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 accediamo all'attributo class:

Fun.dynprop

che stampa:

search in meta
'Meta'

Quindi abbiamo invocato il 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 overriden:

'Not-Meta'

Beh, questo non è male considerando che possiamo reindirizzare usando le metaclassi agli attributi precedentemente definiti ma sovrascritti senza creare un'istanza. Questo esempio è l'opposto di Enum , in cui si definiscono gli attributi nella sottoclasse:

from enum import Enum

class Fun(Enum):
    name = 'me'
    age = 28
    hair = 'brown'

e desidera accedere a questi attributi definiti successivamente per impostazione predefinita.

Fun.name
# <Fun.name: 'me'>

ma vuoi anche consentire l'accesso all'attributo name che è stato definito come DynamicClassAttribute (che restituisce il nome della variabile):

Fun('me').name
# 'name'

perché altrimenti come si può accedere al nome di 28 ?

Fun.hair.age
# <Fun.age: 28>
# BUT:
Fun.hair.name
# returns 'hair'

Vedi 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 "rilasciarla" 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 che possono spiegarlo molto meglio di così posso saltare in profondità (e non sono sicuro se Potrei risolverlo in breve tempo).

Casi d'uso reali potrebbero essere scarsi ma, visto il tempo, si può pensare a qualche ...

Discussione molto bella sulla documentazione di DynamicClassAttribute : "l'abbiamo aggiunta perché ne avevamo bisogno"





python-3.4