Неявно вызывающий инициализатор родительского класса - PullRequest
7 голосов
/ 01 марта 2010
class A(object):
    def __init__(self, a, b, c):
        #super(A, self).__init__()
        super(self.__class__, self).__init__()


class B(A):
    def __init__(self, b, c):
        print super(B, self)
        print super(self.__class__, self)
        #super(B, self).__init__(1, b, c)
        super(self.__class__, self).__init__(1, b, c)

class C(B):
    def __init__(self, c):
        #super(C, self).__init__(2, c)
        super(self.__class__, self).__init__(2, c)
C(3)

В приведенном выше коде закомментированные вызовы __init__ представляются наиболее распространенным «умным» способом инициализации суперкласса. Однако в случае, если иерархия классов, вероятно, изменится, до недавнего времени я использовал незакомментированную форму.

Похоже, что при вызове суперконструктора для B в вышеприведенной иерархии этот B.__init__ вызывается снова, self.__class__ на самом деле C, а не B, как я всегда предполагал. 1010 *

Есть ли в Python-2.x какой-то способ, которым я могу поддерживать надлежащий MRO (в отношении инициализации всех родительских классов в правильном порядке) при вызове суперконструкторов, не называя текущий класс (B in in super(B, self).__init__(1, b, c))?

Ответы [ 4 ]

3 голосов
/ 03 мая 2010

Краткий ответ: нет, нет способа неявно вызвать правильный __init__ с правильными аргументами правого родительского класса в Python 2.x.

Кстати, код, показанный здесь, неверен: если вы используете super (). __init__, то все классы в вашей иерархии должны иметь одинаковую подпись в своих __init__ методах. В противном случае ваш код может перестать работать, если вы введете новый подкласс, который использует множественное наследование.

См. http://fuhm.net/super-harmful/ для более подробного описания проблемы (с изображениями).

1 голос
/ 30 апреля 2010

Ваш код не имеет ничего общего с порядком разрешения методов. Разрешение метода приходит в случае множественного наследования, что не относится к вашему примеру. Ваш код просто неверен, потому что вы предполагаете, что self.__class__ на самом деле является тем же классом, в котором определен метод, и это неправильно:

>>> class A(object):
...     def __init__(self):
...         print self.__class__
... 
>>> 
>>> class B(A):
...     def __init__(self):
...         A.__init__(self)
... 
>>> B()
<class '__main__.B'>
<__main__.B object at 0x1bcfed0>
>>> A()
<class '__main__.A'>
<__main__.A object at 0x1bcff90>
>>> 

поэтому, когда вы должны позвонить:

super(B, self).__init__(1, b, c)

Вы действительно звоните:

# super(self.__class__, self).__init__(1, b, c)
super(C, self).__init__(1, b, c)

РЕДАКТИРОВАТЬ : пытаясь лучше ответить на вопрос.

class A(object):
    def __init__(self, a):
        for cls in self.__class__.mro():
            if cls is not object:
                cls._init(self, a)
    def _init(self, a):
        print 'A._init'
        self.a = a

class B(A):
    def _init(self, a):
        print 'B._init'

class C(A):
    def _init(self, a):
        print 'C._init'

class D(B, C):
    def _init(self, a):
        print 'D._init'


d = D(3)
print d.a

печать:

D._init
B._init
C._init
A._init
3

(модифицированная версия шаблона ).

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

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

Хорошие чтения: how-do-pythons-super-do-the-right-thing и ссылки, предложенные в этом вопросе, и в частности Python Super - отличный, но вы можете ' не использовать это

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

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

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

Ex:

>>> class MetaSuper(type):
...     """adding .__super"""
...     def __init__(cls, name, bases, d):
...         super(MetaSuper, cls).__init__(name, bases, d)
...         if hasattr(cls, "_%s__super" % name):
...             raise AttributeError, "Class has same name as one of its super classes"
...         setattr(cls, "_%s__super" % name, super(cls))
... 
>>> class A:
...  __metaclass__ = MetaSuper
...  def __init__(self, a):
...   self.a = a
...   print 'A.__init__'
... 
>>> class B(A):
...  def __init__(self, a):
...   print 'B.__init__'
...   self.__super.__init__(a)
... 
>>> b = B(42)
B.__init__
A.__init__
>>> b.a
42
>>> 
1 голос
/ 01 марта 2010

Возможно, вы ищете метаклассы?

class metawrap(type):
    def __new__(mcs,name, bases, dict):
        dict['bases'] = bases
        return type.__new__(mcs,name,bases,dict)

class A(object):
    def __init__(self):
        pass
    def test(self):
        print "I am class A"

class B(A):
    __metaclass__ = metawrap
    def __init__(self):
        pass
    def test(self):
        par = super(self.bases[0],self)
        par.__thisclass__.test(self)
foo = B()
foo.test()

Отпечатки "Я класс А"

То, что делает метакласс, переопределяет первоначальное создание класса B (не объекта) и гарантирует, что встроенный словарь для каждого объекта B теперь содержит массив базовых классов, в котором вы можете найти все базовые классы для B

0 голосов
/ 04 мая 2010

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

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

Пример сохранения ссылок на самом объекте путем реализации __new__ на базовом классе:

def mangle(cls, name):
    if not name.startswith('__'):
        raise ValueError('name must start with double underscore')
    return '_%s%s' % (cls.__name__, name)

class ClassStasher(object):
    def __new__(cls, *args, **kwargs):
        obj = object.__new__(cls)
        for c in cls.mro():
            setattr(obj, mangle(c, '__class'), c)
        return obj

class A(ClassStasher):
    def __init__(self):
        print 'init in A', self.__class
        super(self.__class, self).__init__()

class B(A):
    def __init__(self):
        print 'init in B', self.__class
        super(self.__class, self).__init__()

class C(A):
    def __init__(self):
        print 'init in C', self.__class
        super(self.__class, self).__init__()

class D(B, C):
    def __init__(self):
        print 'init in D', self.__class
        super(self.__class, self).__init__()


d = D()    
print d

И, делая аналогичные вещи, но используя мета-класс и сохраняя ссылки __class на самих объектах класса:

class ClassStasherType(type):
    def __init__(cls, name, bases, attributes):
        setattr(cls, mangle(cls, '__class'), cls)

class ClassStasher(object):
    __metaclass__ = ClassStasherType

class A_meta(ClassStasher):
    def __init__(self):
        print 'init in A_meta', self.__class
        super(self.__class, self).__init__()

class B_meta(A_meta):
    def __init__(self):
        print 'init in B_meta', self.__class
        super(self.__class, self).__init__()

class C_meta(A_meta):
    def __init__(self):
        print 'init in C_meta', self.__class
        super(self.__class, self).__init__()

class D_meta(B_meta, C_meta):
    def __init__(self):
        print 'init in D_meta', self.__class
        super(self.__class, self).__init__()


d = D_meta()    
print d

Запуск всего этого вместе как одного исходного файла:

% python /tmp/junk.py
init in D <class '__main__.D'>
init in B <class '__main__.B'>
init in C <class '__main__.C'>
init in A <class '__main__.A'>
<__main__.D object at 0x1004a4a50>
init in D_meta <class '__main__.D_meta'>
init in B_meta <class '__main__.B_meta'>
init in C_meta <class '__main__.C_meta'>
init in A_meta <class '__main__.A_meta'>
<__main__.D_meta object at 0x1004a4bd0>
...