2011-01-05 3 views
5

Посмотрите на следующий код:Как получить доступ к атрибутам класса суперкласса в Python?

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) 

У меня есть иерархия классов каждый со своим собственным словарем, содержащим некоторые значения по умолчанию. Если экземпляр класса не имеет определенного атрибута, вместо него следует вернуть значение по умолчанию. Если значение по умолчанию для атрибута не содержится в словаре defaults текущего класса, следует искать словарь defaults суперкласса.

Я пытаюсь реализовать это, используя метод рекурсивного класса get_default. К сожалению, программа застряла в бесконечной рекурсии. Мое понимание super() явно отсутствует. Получив доступ к __mro__, я могу заставить его работать исправно, но я не уверен, что это правильное решение.

У меня такое чувство, что ответ находится где-то в this article, но я еще не смог его найти. Возможно, мне нужно прибегнуть к использованию метакласса?

Редактировать: В моей заявке __getattr__ первые чеки self.base. Если это не None, атрибут должен быть получен оттуда. Только в другом случае необходимо вернуть значение по умолчанию. Возможно, я мог бы переопределить __getattribute__. Будет ли это лучшим решением?

Редактировать 2: Ниже приведен расширенный пример функциональности, которую я ищу. В настоящее время он реализуется с использованием __mro__ (предыдущее предложение unutbu, в отличие от моего первоначального рекурсивного метода). Если кто-то не может предложить более элегантное решение, я счастлив использовать эту реализацию. Надеюсь, это прояснит ситуацию.

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 

Выход:

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

Как насчет размещения значений по умолчанию непосредственно в пространстве имен классов вместо использования словаря 'defaults'? Это сделало бы любую магию ненужной - она ​​просто сработала бы. –

+0

@Sven Я знал, что должен был упомянуть об этом :) В моем приложении '__getattr__' сначала проверяет другой атрибут (база). Только если другой атрибут None, значение по умолчанию должно быть возвращено. В противном случае атрибут нужно искать в базе. –

+0

Да, вот как атрибуты экземпляра/класса работают в Python. :) Проверьте ответ Тони. Afaik его код будет делать точно так же, как ваш. –

ответ

0

Решение, предложенное во втором редактировании вопроса, по-прежнему остается единственным, обеспечивающим все, что требует мое приложение. Хотя код unutbu может быть проще понять, решение __mro__ дает некоторые преимущества IMO (см. Комментарии).

0

Вы должны использовать name mangling здесь.

Rename defaults к __defaults

Это дает каждому классу однозначный атрибут, чтобы они не путаются друг с другом

8

не совсем ответ, но наблюдение:

Это выглядит overengineered для меня, обычная ловушка, когда ищет оправдания для использования магии питона.

Если вам побеспокоено определить defaults dict для класса, почему бы не просто определить атрибуты вместо этого? эффект тот же.

class A: 
    a = 1 

class B(A): 
    b = 2 

class C(B): 
    c = 3 


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

EDIT:

Что касается ответа на вопрос, я бы, вероятно, использовать __getattribute__ в сочетании с моим предложением, как это:

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

Стоит отметить, что вы также можете установить атрибут на c. Так что вы на практике получаете настройку экземпляра со значением по умолчанию, определенным в классе. –

+0

Это не подходит для использования, я добавил к вопросу. –

+0

@Brecht Machiels: Да, будет. Попробуй. –

1

Как насчет:

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

Если base * не * 'None', ему необходимо вернуть соответствующий атрибут базы. Но я думаю, мы не можем получить имя атрибута в функции обертки? –

+0

@Brecht Machiels: действительно возможно передать дополнительные параметры функции 'check_base_first', к которой можно получить доступ из' wrapper'. – unutbu

+1

@Brecht Machiels: Хотя может быть способ использовать свойства, чтобы делать то, что вы хотите, это может быть неправильное решение. Проблема, с которой вы столкнулись, может быть симптомом плохого дизайнерского решения ранее. Если вы пересматриваете свой вопрос, чтобы точно показать, как вы хотите, чтобы ваши объекты вели себя (с 'base's), возможно, может быть предложен лучший дизайн. – unutbu

2

Я думаю, что проблема связана с ошибкой выискивают цель super().

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

По существу, упаковка вашего объекта (или класса) в супер() делает Python пропустить наиболее недавно унаследованный класс, делая поиск атрибута. В вашем коде это приводит к тому, что класс C пропускается при поиске get_default, но это на самом деле ничего не делает, поскольку C не определяет get_default в любом случае. Естественно, это приводит к бесконечному циклу.

Решение определить эту функцию в каждом классе, производный от А. Это может быть сделано с помощью метакласса:

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) 

результаты:

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) 

следует отметить, что большинство Python люди посчитают это очень странным решением, и вы должны использовать это, только если вам действительно нужен, возможно, для поддержки устаревшего кода.

1

Чтобы узнать больше о вашем «базовом» и «стандартном» случаях.

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

Это покрывает ваш заявленный прецедент. Вам не нужна магия. Если ваш usecase несколько отличается, объясните, что это такое, и мы расскажем вам, как это сделать без магии. ;)

Смежные вопросы