Вопрос

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

Это было полезно?

Решение

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

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

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

Другие советы

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

Хорошее чтение: как-питоны-супер-поступают-правильно и ссылки, предложенные в этом вопросе и, в частности, Python Super — это здорово, но вы не можете его использовать.

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

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

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

Бывший:

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

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

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.

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

Методы в данном определении класса всегда преобразуют атрибуты двойного подчеркивания, чтобы включить имя класса, в котором они определены.Итак, если вы спрятали ссылку на класс в форме с искаженным именем, чтобы ее могли видеть экземпляры, вы можете использовать ее при вызове 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>
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top