Неявно вызывающий инициализатор родительского класса

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 поскольку я всегда принимал.

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

7
задан Matt Joiner 30 April 2010 в 20:34
поделиться

4 ответа

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

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

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

3
ответ дан 7 December 2019 в 12:19
поделиться

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

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

Выводит «Я класс A»

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

1
ответ дан 7 December 2019 в 12:19
поделиться

Ваш код не имеет никакого отношения к порядку разрешения методов. Разрешение методов происходит в случае множественного наследования, что не относится к вашему примеру. Ваш код просто неверен, потому что вы предполагаете, что 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)

EDIT: попытка лучше ответить на вопрос.

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

prints:

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

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

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

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

Хорошее чтение: how-does-pythons-super-do-the-right-thing и ссылки, предложенные в этом вопросе и, в частности, Python's Super is nifty, but you can't use it

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

EDIT 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
ответ дан 7 December 2019 в 12:19
поделиться

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

Методы в данном определении класса всегда искажают атрибуты с двойным подчеркиванием, чтобы включить имя класса, в котором они определены. Итак, если вы поместите ссылку на класс в измененной форме имени, где экземпляры могут ее видеть, вы можете использовать это при звонке на 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>
0
ответ дан 7 December 2019 в 12:19
поделиться
Другие вопросы по тегам:

Похожие вопросы: