如何通过手动填充__class__单元格来使super()工作?

nik*_*kow 10 python metaprogramming super python-3.x

在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()
Run Code Online (Sandbox Code Playgroud)

它失败是因为super()查找了一个在这种情况下未定义的__class__ 单元格.

是否可以在定义功能后手动设置此单元格,还是不可能?

不幸的是,我不明白细胞是如何在这种情况下工作的(没有找到很多文档).我希望有类似的东西

__init__.__class_cell_thingy__ = C
Run Code Online (Sandbox Code Playgroud)

当然我只会在类赋值明确/唯一的情况下使用它(在我的情况下,将类添加到类的整个过程是自动化的,所以添加这样的行很简单).

Gle*_*ard 21

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

但是,对于Python的高级用户来说理解这一点很有用,所以我会解释它.

Cells和freevars是创建闭包时分配的值.例如,

def f():
    a = 1
    def func():
        print(a)
    return func
Run Code Online (Sandbox Code Playgroud)

f返回一个基于的闭包func,存储一个引用a.该引用存储在一个单元格中(实际上存在于freevar中,但哪一个取决于实现).你可以检查一下:

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

("细胞"和"freevars"非常相似.Freevars有名称,其中细胞有索引.它们都存储在func.__closure__,细胞首先存在.我们只关心freevars这里,因为那__class__就是.)

一旦你理解了,你就可以看到super()实际上是如何工作的.任何包含调用的函数super实际上都是一个闭包,其中有一个freevar命名__class__(如果你__class__自己引用它也会添加):

class foo:
    def bar(self):
        print(__class__)
Run Code Online (Sandbox Code Playgroud)

(警告:这是事情变得邪恶的地方.)

这些细胞是可见的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()
Run Code Online (Sandbox Code Playgroud)