隐式调用父类初始化器

发布于 2024-08-23 03:40:33 字数 895 浏览 10 评论 0原文

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 中是否有某种方法可以在调用超级构造函数而不命名当前类(B in super(B, self).__init__(1, b, c))?

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)

In the above code, the commented out __init__ calls appear to the be the commonly accepted "smart" way to do super class initialization. However in the event that the class hierarchy is likely to change, I have been using the uncommented form, until recently.

It appears that in the call to the super constructor for B in the above hierarchy, that B.__init__ is called again, self.__class__ is actually C, not B as I had always assumed.

Is there some way in Python-2.x that I can maintain proper MRO (with respect to initializing all parent classes in the correct order) when calling super constructors while not naming the current class (the B in in super(B, self).__init__(1, b, c))?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

往事风中埋 2024-08-30 03:40:33

简短的回答:不,在 Python 2.x 中,无法使用正确父类的正确参数隐式调用正确的 __init__

顺便说一句,此处显示的代码是不正确的:如果您使用 super().__init__,则层次结构中的所有类都必须在其 __init__ 方法中具有相同的签名。否则,如果您引入使用多重继承的新子类,您的代码可能会停止工作。

有关问题的详细说明,请参阅 http://fuhm.net/super-harmful/ (有图片)。

Short answer: no, there's no way to implicitly invoke the right __init__ with the right arguments of the right parent class in Python 2.x.

Incidentally, the code as shown here is incorrect: if you use super().__init__, then all classes in your hierarchy must have the same signature in their __init__ methods. Otherwise your code can stop working if you introduce a new subclass that uses multiple inheritance.

See http://fuhm.net/super-harmful/ for a longer description of the issue (with pictures).

蓝海 2024-08-30 03:40:33

您的代码与方法解析顺序无关。方法解析出现在多重继承的情况下,这不是您的示例的情况。您的代码完全错误,因为您假设 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-does-pythons-super-do-the-正确的事情以及该问题中建议的链接,特别是Python的Super很漂亮,但是你可以不要使用它

如果层次结构可能会发生变化,则这是不良设计的症状,并且会对使用该代码的所有部分产生影响,因此不应鼓励这样做。

编辑2

我想到了另一个例子,但它使用元类。 Urwid 库使用元类来存储属性,__super< /code>,在类中,这样您只需要访问该属性即可。

前任:

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

Your code has nothing to do with method resolution order. Method resolution comes in the case of multiple inheritance which is not the case of your example. Your code is simply wrong because you assume that self.__class__ is actually the same class of the one where the method is defined and this is wrong:

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

so when you should call:

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

you are indeed calling:

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

EDIT: trying to better answer the question.

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

(A modified version of template pattern).

Now parents' methods are really called implicitly, but i have to agree with python zen where explicit is better than implicit because the code is lesser readable and the gain is poor. But beware that all _init methods have the same parameters, you cannot completely forget about parents and I don't suggest to do so.

For single inheritance, a better approach is explicitly calling parent's method, without invoking super. Doing so you don't have to name the current class, but still you must care about who is the parent's class.

Good reads are: how-does-pythons-super-do-the-right-thing and the links suggested in that question and in particularity Python's Super is nifty, but you can't use it

If hierarchy is likely to change is symptoms of bad design and has consequences in all the parts who are using that code and should not be encouraged.

EDIT 2

Another example comes me in mind, but which uses metaclasses. Urwid library uses metaclass to store an attribute, __super, in class so that you need just to access to that attribute.

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
>>> 
征棹 2024-08-30 03:40:33

也许您正在寻找的是元类?

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 的所有基类

Perhaps what you are looking for is metaclasses?

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

Prints "I am class A"

What the metaclass does is overriding the initial creation of the B class (not the object) and makes sure that the builtin dictionary for each B object now contains a bases array where you can find all the baseclasses for B

万劫不复 2024-08-30 03:40:33

据我所知,以下操作并不常见。但它似乎确实有效。

给定类定义中的方法始终会破坏双下划线属性以包含定义它们的类的名称。因此,如果您以名称破坏的形式存储对该类的引用,实例可以看到它,则可以使用在对 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>

To my knowledge, the following isn't commonly done. But it does seem to work.

Methods in a given class definition always mangle double-underscore attributes to include the name of the class they're defined in. So, if you stash a reference to the class in name-mangled form where the instances can see it, you can use that in the call to super.

An example stashing the references on the object itself, by implementing __new__ on the baseclass:

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

And, doing a similar thing, but using a meta-class and stashing the __class references on the class objects themselves:

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

Running this all together, as one source file:

% 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>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文