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

发布于 2021-01-29 15:26: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__
单元格

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

不幸的是,我不了解单元在这种情况下的工作方式(找不到太多的文档)。我希望有类似的东西

__init__.__class_cell_thingy__ = C

当然,我只会在类分配明确/唯一的情况下使用此方法(在我的情况下,将方法自动添加到类中的整个过程是自动化的,因此添加这样的行将很简单)。

关注者
0
被浏览
40
1 个回答
  • 面试哥
    面试哥 2021-01-29
    为面试而生,有面试问题,就找面试哥。

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

    但是,这对于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()
    


知识点
面圈网VIP题库

面圈网VIP题库全新上线,海量真题题库资源。 90大类考试,超10万份考试真题开放下载啦

去下载看看