如何通过手动填充__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__
单元格。
定义功能后是否可以手动设置此单元格,还是不可能?
不幸的是,我不了解单元在这种情况下的工作方式(找不到太多的文档)。我希望有类似的东西
__init__.__class_cell_thingy__ = C
当然,我只会在类分配明确/唯一的情况下使用此方法(在我的情况下,将方法自动添加到类中的整个过程是自动化的,因此添加这样的行将很简单)。
-
说真的:您真的不想这样做。
但是,这对于Python的高级用户了解这一点很有用,因此我将对其进行解释。
单元格和freevar是创建闭包时分配的值。例如,
def f(): a = 1 def func(): print(a) return func
f
返回基于的闭包func
,并存储对的引用a
。该引用存储在一个单元中(实际上存储在一个freevar中,但这取决于实现)。您可以检查以下内容:myfunc = f() # ('a',) print(myfunc.__code__.co_freevars) # (<cell at 0xb7abce84: int object at 0x82b1de0>,) print(myfunc.__closure__)
(“ cells”和“
freevars”非常相似。Freevars具有名称,其中的单元格具有索引。它们都存储在中func.__closure__
,单元格在前。我们只在freevars
这里关心,因为那样的话__class__
。)一旦了解了这一点,就可以看到super()的实际工作原理。任何包含对的调用的函数
super
实际上都是一个闭包,具有一个名为freevar的闭包__class__
(如果您引用__class__
自己,也会添加该闭包):class foo: def bar(self): print(__class__)
(警告:这就是事情变得邪恶的地方。)
这些单元格在中可见
func.__closure__
,但是是只读的。你不能改变它。更改它的唯一方法是创建一个新函数,该新函数由types.FunctionType
构造函数完成。但是,您的__init__
函数根本没有__class__
freevar,因此我们需要添加一个。这意味着我们还必须创建一个新的代码对象。下面的代码执行此操作。我
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()