Come accedere a una superclasse attributi di classe in Python?

Un’occhiata al il seguente codice:

class A(object):
    defaults = {'a': 1}

    def __getattr__(self, name):
        print('A.__getattr__')
        return self.get_default(name)

    @classmethod
    def get_default(cls, name):
        # some debug output
        print('A.get_default({}) - {}'.format(name, cls))
        try:
            print(super(cls, cls).defaults) # as expected
        except AttributeError: #except for the base object class, of course
            pass

        # the actual function body
        try:
            return cls.defaults[name]
        except KeyError:
            return super(cls, cls).get_default(name) # infinite recursion
            #return cls.__mro__[1].get_default(name) # this works, though

class B(A):
    defaults = {'b': 2}

class C(B):
    defaults = {'c': 3}


c = C()
print('c.a =', c.a)

Ho una gerarchia di classi, ognuna con il suo dizionario contenente alcuni valori di default. Se un’istanza di una classe non ha un particolare attributo, un valore di default per questo deve essere restituito invece. Se no valore predefinito per l’attributo è contenuta nell’attuale classe defaults dizionario, la superclasse defaults dizionario dovrebbe essere cercato.

Sto cercando di implementare questo uso della ricorsione metodo della classe get_default. Il programma rimane bloccato in una ricorsione infinita, purtroppo. La mia comprensione di super() è ovviamente manca. Accedendo __mro__, posso farlo funzionare correttamente, ma non sono sicuro che questa è una soluzione adeguata.

Ho la sensazione che la risposta è da qualche parte in questo articolo, ma non sono stato in grado di trovare sicurezza. Forse ho bisogno di ricorrere all’uso di una metaclass?

edit: Nella mia applicazione, __getattr__ controlla prima self.base. Se non è None, l’attributo deve essere recuperata. Solo nel caso di un valore predefinito deve essere restituito. Probabilmente potrei ignorare __getattribute__. Sarebbe la soluzione migliore?

edit 2: Sotto è riportato un esteso esempio di funzionalità che sto cercando. Attualmente è implementato utilizzando __mro__ (unutbu precedente suggerimento, rispetto al mio originale metodo ricorsivo). A meno che qualcuno può suggerire una soluzione più elegante, io sono felice di questa implementazione. Spero che questo cancella le cose.

class A(object):
    defaults = {'a': 1}

    def __init__(self, name, base=None):
        self.name = name
        self.base = base

    def __repr__(self):
        return self.name

    def __getattr__(self, name):
        print(" '{}' attribute not present in '{}'".format(name, self))
        if self.base is not None:
            print("  getting '{}' from base ({})".format(name, self.base))
            return getattr(self.base, name)
        else:
            print("  base = None; returning default value")
            return self.get_default(name)

    def get_default(self, name):
        for cls in self.__class__.__mro__:
            try:
                return cls.defaults[name]
            except KeyError:
                pass
        raise KeyError

class B(A):
    defaults = {'b': 2}

class C(B):
    defaults = {'c': 3}


c1 = C('c1')
c1.b = 55

print('c1.a = ...'); print('   ...', c1.a) # 1
print(); print('c1.b = ...'); print('   ...', c1.b) # 55
print(); print('c1.c = ...'); print('   ...', c1.c) # 3

c2 = C('c2', base=c1)
c2.c = 99

print(); print('c2.a = ...'); print('   ...', c2.a) # 1
print(); print('c2.b = ...'); print('   ...', c2.b) # 55
print(); print('c2.c = ...'); print('   ...', c2.c) # 99

Uscita:

c1.a = ...
 'a' attribute not present in 'c1'
  base = None; returning default value
   ... 1

c1.b = ...
   ... 55

c1.c = ...
 'c' attribute not present in 'c1'
  base = None; returning default value
   ... 3

c2.a = ...
 'a' attribute not present in 'c2'
  getting 'a' from base (c1)
 'a' attribute not present in 'c1'
  base = None; returning default value
   ... 1

c2.b = ...
 'b' attribute not present in 'c2'
  getting 'b' from base (c1)
   ... 55

c2.c = ...
   ... 99
Come su come effettuare le impostazioni predefinite direttamente nello spazio dei nomi della classe, invece di usare il dizionario defaults? Questo farebbe qualsiasi magia inutili — sarebbe solo di lavoro.
Sapevo che avrei detto questo 🙂 Nella mia applicazione, il __getattr__ è in primo luogo controllare un altro attributo (base). Solo se l’altro attributo è Nessuno, il valore di default dovrebbe essere restituiti. In caso contrario, l’attributo deve essere cercato nella base.
Sì, come istanza/classe di attributi di lavoro in Python. 🙂 Controllo di Toni di risposta. Afaik il suo codice fa esattamente lo stesso come il tuo.

OriginaleL’autore Brecht Machiels | 2011-01-05

6 risposte

  1. 8

    Non è davvero una risposta, ma un’osservazione:

    Questo sembra overengineered per me, una trappola comune quando si cerca scuse per usare python magia.

    Se si può essere preso la briga di definire un defaults dict per una classe perché non solo di definire gli attributi, invece? l’effetto è lo stesso.

    class A:
        a = 1
    
    class B(A):
        b = 2
    
    class C(B):
        c = 3
    
    
    c = C()
    print('c.a =', c.a)

    EDIT:

    Come per rispondere alla domanda, io probabilmente usare __getattribute__ in combinazione con il mio suggerimento come questo:

    def __getattribute__(self, name):
        try:
            return object.__getattribute__(self.base, name)
        except AttributeError:
            return object.__getattribute__(self, name)
    Vale la pena ricordare che è possibile impostare l’attributo c. In modo che, in pratica, di ottenere un’istanza di impostazione, il valore predefinito è definito nella classe.
    Questo non potrà soddisfare per il caso di utilizzo ho aggiunto alla domanda.
    Machiels: si, funziona. Provare.

    OriginaleL’autore Toni Ruža

  2. 2

    Penso che il guaio risultati di incomprensione con lo scopo di super().

    http://docs.python.org/library/functions.html#super

    Essenzialmente, il confezionamento dell’oggetto (o di classe), super() rende Python saltare più recentemente in classe ereditata quando si fa un attributo di ricerca. Nel codice, questo si traduce in classe C di essere ignorato quando si cerca get_default, ma questo non davvero fare qualcosa, dato che il C non definisce un get_default comunque. Naturalmente, questo si traduce in un ciclo infinito.

    La soluzione è di definire questa funzione in ogni classe che deriva da A.
    Questo può essere fatto utilizzando una metaclass:

    class DefaultsClass(type):
        def __init__(cls, name, bases, dct):
    
            def get_default(self, name):
                # some debug output
                print('A.get_default(%s) - %s' % (name, cls))
                try:
                    print(cls.defaults) # as expected
                except AttributeError: #except for the base object class, of course
                    pass
    
                # the actual function body
                try:
                    return cls.defaults[name]
                except KeyError:
                    return super(cls, self).get_default(name) # cooperative superclass
    
            cls.get_default = get_default
            return super(DefaultsClass, cls).__init__(name, bases, dct)
    
    class A(object):
        defaults = {'a': 1}
        __metaclass__ = DefaultsClass
    
        def __getattr__(self, name):
            return self.get_default(name)
    
    
    
    class B(A):
        defaults = {'b': 2}
    
    class C(B):
        defaults = {'c': 3}
    
    
    c = C()
    print('c.a =', c.a)
    print('c.b =', c.b)
    print('c.c =', c.c)

    risultati:

    A.get_default(c) - <class '__main__.C'>
    {'c': 3}
    ('c.c =', 3)
    A.get_default(b) - <class '__main__.C'>
    {'c': 3}
    A.get_default(b) - <class '__main__.B'>
    {'b': 2}
    ('c.b =', 2)
    A.get_default(a) - <class '__main__.C'>
    {'c': 3}
    A.get_default(a) - <class '__main__.B'>
    {'b': 2}
    A.get_default(a) - <class '__main__.A'>
    {'a': 1}
    ('c.a =', 1)

    Devo notare che la maggior parte di Python la gente prenderà in considerazione questa bizzarra soluzione, e si dovrebbe utilizzare solo se si davvero necessario, forse per il supporto legacy codice.

    OriginaleL’autore bukzor

  3. 1

    Di essere più chiaro circa la vostra “base” vs “default” caso.

    >>> class A(object):
    ...     a = 1
    ... 
    >>> class B(A):
    ...     b = 2
    ... 
    >>> class C(B):
    ...     c = 3
    ... 
    >>> a = A()
    >>> b = B()
    >>> c = C()
    >>> 
    >>> b.b = 23
    >>> b.a
    1
    >>> b.b
    23
    >>> c.a
    1
    >>> c.b
    2
    >>> c.c
    3
    >>> c.c = 45
    >>> c.c
    45

    Questo copre il tuo affermato in caso d’uso. Non hai bisogno di magia. Se il tuo caso d’uso è un po ‘ diverso, spiegare di cosa si tratta, e ti diremo come fare senza magia. 😉

    OriginaleL’autore Lennart Regebro

  4. 1

    Come su:

    class A(object):
        def __init__(self,base=None):
            self.a=1
            if base is not None:
                self.set_base(base)
            super(A,self).__init__() 
        def set_base(self,base):
            for key in ('a b c'.split()):
                setattr(self,key,getattr(base,key))
    class B(A): 
        def __init__(self,base=None):
            self.b=2
            super(B,self).__init__(base)        
    class C(B): 
        def __init__(self,base=None):
            self.c=3
            super(C,self).__init__(base)
    
    c1=C()
    c1.b=55
    print(c1.a)
    print(c1.b)
    print(c1.c)
    # 1
    # 55
    # 3
    
    c2=C(c1)
    c2.c=99
    print(c2.a)
    print(c2.b)
    print(c2.c)
    # 1
    # 55
    # 99
    
    c1.set_base(c2)
    print(c1.a)
    print(c1.b)
    print(c1.c)
    # 1
    # 55
    # 99
    Se la base è non None, deve restituire il corrispondente attributo di base. Ma immagino che non possiamo ottenere il nome dell’attributo in funzione wrapper?
    Machiels: è infatti possibile passare dei parametri aggiuntivi per la check_base_first funzione, che può essere letta da wrapper.
    Machiels: anche se ci potrebbe essere un modo per utilizzare le proprietà per fare quello che vuoi, potrebbe non essere la soluzione giusta. Il problema che si trovano ad affrontare potrebbe essere un sintomo di una cattiva decisione di progettazione in precedenza. Se puoi rivedere la tua domanda, per mostrare esattamente come volete che i vostri oggetti di comportarsi con bases) forse meglio il design può essere suggerito.
    Questa soluzione è statico, come contrario alla soluzione proposta in questione, tuttavia. Un oggetto non vedere le eventuali modifiche apportate alla sua base o i valori di default. A seconda dell’applicazione, questo può essere un vantaggio o uno svantaggio. Per me, preferisco la soluzione dinamica.
    Ho aggiunto un set_base metodo, in modo che è possibile modificare base in modo dinamico.

    OriginaleL’autore unutbu

  5. 0

    Si dovrebbe usare name mangling qui.

    Rinominare defaults per __defaults

    Che dà ad ogni classe un univoco attributo in modo che non vengano confusi con l’altro

    OriginaleL’autore John La Rooy

  6. 0

    La soluzione proposta nella seconda modifica della domanda è ancora l’unico che fornisce tutto ciò che l’applicazione richiede. Mentre unutbu codice potrebbe essere più semplice da capire, il __mro__ soluzione fornisce alcuni vantaggi IMO (vedi commenti).

    OriginaleL’autore Brecht Machiels

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *