Pergunta

Dê uma olhada no seguinte código:

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)

Eu tenho uma hierarquia de classes, cada uma com seu próprio dicionário contendo alguns valores padrão. Se uma instância de uma classe não tiver um atributo específico, um valor padrão deve ser retornado. Se nenhum valor padrão para o atributo estiver contido na classe atual defaults dicionário, a superclasse defaults O dicionário deve ser pesquisado.

Estou tentando implementar isso usando o método de classe recursiva get_default. O programa fica preso em uma recursão infinita, infelizmente. Minha compreensão de super() obviamente está faltando. Acessando __mro__, Posso fazer com que funcione corretamente, mas não tenho certeza se essa é uma solução adequada.

Eu tenho a sensação de que a resposta está em algum lugar em Este artigo, mas ainda não consegui encontrá -lo. Talvez eu precise recorrer ao uso de um metaclasse?

editar: Na minha aplicação, __getattr__ Primeiros cheques self.base. Se não é None, o atributo precisa ser buscado a partir daí. Somente no outro caso, um valor padrão deve ser retornado. Eu provavelmente poderia substituir __getattribute__. Essa seria a melhor solução?

Editar 2: Abaixo está um exemplo estendido da funcionalidade que estou procurando. Atualmente é implementado usando __mro__ (A sugestão anterior da Unutbu, em oposição ao meu método recursivo original). A menos que alguém possa sugerir uma solução mais elegante, fico feliz em usar essa implementação. Eu espero que isso esclareça as coisas.

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

A saída:

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
Foi útil?

Solução 6

A solução proposta na segunda edição da pergunta ainda é a única que fornece tudo o que meu aplicativo exige. Embora o código da Unutbu possa ser mais simples de entender, o __mro__ A solução fornece algumas vantagens IMO (consulte os comentários).

Outras dicas

Não é realmente uma resposta, mas uma observação:

Isso me parece exagerado para mim, uma armadilha comum ao procurar desculpas para usar a magia do Python.

Se você pode se incomodar em definir um defaults Dict para uma aula, por que não definir apenas os atributos? o efeito é o mesmo.

class A:
    a = 1

class B(A):
    b = 2

class C(B):
    c = 3


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

EDITAR:

Quanto a responder à pergunta, eu provavelmente usaria __getattribute__ em combinação com minha sugestão como esta:

def __getattribute__(self, name):
    try:
        return object.__getattribute__(self.base, name)
    except AttributeError:
        return object.__getattribute__(self, name)

Eu acho que o problema resulta de mal -entendidos do propósito de super().

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

Essencialmente, envolver seu objeto (ou classe) em Super () faz com que o Python pule a classe mais recentemente inetçada ao fazer a pesquisa de atributos. No seu código, isso resulta na ignição da classe C ao procurar get_default, mas isso realmente não faz nada, já que C não define um get_default de qualquer maneira. Naturalmente, isso resulta em um loop infinito.

A solução é definir essa função em cada classe que deriva de A. Isso pode ser feito usando um metaclasse:

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)

resultados:

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 notar que a maioria das pessoas do Python considerará isso uma solução muito bizarra, e você deve usar isso apenas se você verdade Precisa, talvez para apoiar o código legado.

Para ser mais claro sobre o seu caso "Base" vs "padrão".

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

Isso cobre o seu caso de uso declarado. Você não precisa de magia. Se a sua USECASE for um pouco diferente, explique o que é e vamos lhe dizer como fazer isso sem mágica. ;)

Que tal:

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

Você deveria usar Nome Mangling aqui.

Renomear defaults para __defaults

Isso dá a cada classe um atributo inequívoco para que eles não sejam confundidos um com o outro

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top