如何通过手动填充 __class__ 单元使 super() 生效?

15 投票
3 回答
3824 浏览
提问于 2025-04-16 11:09

在Python 3中,你可以用super()来代替super(MyClass, self),不过这只适用于在类里面定义的方法。正如Michele Simionato的文章所描述的,下面这个例子就不行:

def __init__(self):
    print('calling __init__')
    super().__init__()

class C(object):
    __init__ = __init__

if __name__ == '__main__':
    c = C()

之所以失败,是因为super()在寻找一个__class__cell,但在这种情况下并没有定义这个cell。

有没有办法在函数定义后手动设置这个cell,还是说这根本不可能?

可惜的是,我不太明白在这个上下文中cell是怎么工作的(我没找到太多相关的文档)。我希望能有类似这样的东西:

__init__.__class_cell_thingy__ = C

当然,我只会在类的赋值是明确且唯一的情况下使用这个(在我的情况下,给类添加方法的整个过程都是自动化的,所以加上这样一行代码很简单)。

3 个回答

2

你可以使用函数的字典。

def f(self):
    super(f.owner_cls, self).f()
    print("B")

def add_to_class(cls, member, name=None):
    if hasattr(member, 'owner_cls'):
        raise ValueError("%r already added to class %r" % (member, member.owner_cls))
    member.owner_cls = cls
    if name is None:
        name = member.__name__
    setattr(cls, name, member)

class A:
     def f(self):
         print("A")

class B(A):
     pass

add_to_class(B, f)

B().f()

如果你不想在函数里死死写死成员的名字,你甚至可以添加另一个属性 member_name

2

也许可以,但为什么要这么做呢?无论哪种情况,你都需要明确指定是哪一个类,因为隐式的方法并没有奏效。也许你可以以某种方式明确设置单元格,但这样做没有必要。直接把参数传进去就好了。

def __init__(self):
    print('calling __init__')
    super(self.__class__, self).__init__()

class C(object):
    __init__ = __init__

if __name__ == '__main__':
    c = C()

如果你能直接传入实际的类,那就更好了,比如这样:

def __init__(self):
    print('calling __init__')
    super(C, self).__init__()

class C(object):
    __init__ = __init__

if __name__ == '__main__':
    c = C()

但如果你能这样做,那你可以直接在 C 上放 __init__,所以假设你不能这样做。

27

说真的:你真的不想这样做。

不过,这对高级的Python用户来说是有用的,所以我来解释一下。

当创建一个闭包时,会有一些值被分配,这些值叫做“cells”和“freevars”。举个例子,

def f():
    a = 1
    def func():
        print(a)
    return func

f 这个函数会根据 func 返回一个闭包,并且会保存对 a 的引用。这个引用会存储在一个单元格里(实际上是存储在一个自由变量中,但具体是哪一个取决于实现)。你可以查看这个情况:

myfunc = f()
# ('a',)
print(myfunc.__code__.co_freevars)
# (<cell at 0xb7abce84: int object at 0x82b1de0>,)
print(myfunc.__closure__)

(“cells”和“freevars”非常相似。自由变量有名字,而单元格有索引。它们都存储在 func.__closure__ 中,单元格在前。我们这里只关心 freevars,因为这就是 __class__ 的内容。)

一旦你理解了这一点,你就能明白 super() 实际上是怎么工作的。任何包含对 super 调用的函数实际上都是一个闭包,并且有一个名为 __class__ 的自由变量(如果你自己提到 __class__,这个变量也会被添加):

class foo:
    def bar(self):
        print(__class__)

(警告:这时候事情会变得复杂。)

这些单元格在 func.__closure__ 中是可见的,但它是只读的;你不能更改它。唯一能改变它的方法是创建一个新函数,这可以通过 types.FunctionType 构造器来完成。然而,你的 __init__ 函数根本没有 __class__ 的自由变量——所以我们需要添加一个。这意味着我们还需要创建一个新的代码对象。

下面的代码就是这么做的。我为了演示添加了一个基类 B。这段代码做了一些假设,比如 __init__ 里没有已经存在的名为 __class__ 的自由变量。

这里还有一个小技巧:似乎没有单元格类型的构造函数。为了绕过这个问题,创建了一个虚拟函数 C.dummy,它包含我们需要的单元格变量。

import types

class B(object):
    def __init__(self):
        print("base")

class C(B):
    def dummy(self): __class__

def __init__(self):
    print('calling __init__')
    super().__init__()

def MakeCodeObjectWithClass(c):
    """
    Return a copy of the code object c, with __class__ added to the end
    of co_freevars.
    """
    return types.CodeType(c.co_argcount, c.co_kwonlyargcount, c.co_nlocals,
            c.co_stacksize, c.co_flags, c.co_code, c.co_consts, c.co_names,
            c.co_varnames, c.co_filename, c.co_name, c.co_firstlineno,
            c.co_lnotab, c.co_freevars + ('__class__',), c.co_cellvars)

new_code = MakeCodeObjectWithClass(__init__.__code__)
old_closure = __init__.__closure__ or ()
C.__init__ = types.FunctionType(new_code, globals(), __init__.__name__,
    __init__.__defaults__, old_closure + (C.dummy.__closure__[0],))

if __name__ == '__main__':
    c = C()

撰写回答