如何通过手动填充 __class__ 单元使 super() 生效?
在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 个回答
你可以使用函数的字典。
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
。
也许可以,但为什么要这么做呢?无论哪种情况,你都需要明确指定是哪一个类,因为隐式的方法并没有奏效。也许你可以以某种方式明确设置单元格,但这样做没有必要。直接把参数传进去就好了。
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__
,所以假设你不能这样做。
说真的:你真的不想这样做。
不过,这对高级的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()