Как получить доступ к атрибутам класса суперкласса в Python? - PullRequest
5 голосов
/ 05 января 2011

Посмотрите на следующий код:

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__, я могу заставить его работать должным образом, но я не уверен, что это правильное решение.

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

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

edit 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

Ответы [ 6 ]

8 голосов
/ 05 января 2011

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

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

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

class A:
    a = 1

class B(A):
    b = 2

class C(B):
    c = 3


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

РЕДАКТИРОВАТЬ:

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

def __getattribute__(self, name):
    try:
        return object.__getattribute__(self.base, name)
    except AttributeError:
        return object.__getattribute__(self, name)
2 голосов
/ 06 января 2011

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

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

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

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

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 голос
/ 06 января 2011

Для большей ясности относительно вашего "базового" и "стандартного" случая.

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

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

1 голос
/ 06 января 2011

Как насчет:

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 голосов
/ 11 января 2011

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

0 голосов
/ 05 января 2011

Вы должны использовать имя искажения здесь.

Переименовать defaults в __defaults

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

...