Skip to content Skip to sidebar Skip to footer

__classcell__ Generates Error In Python 3.6 When The Metaclass Calls Multiple Super().__new__ From Inherited Class

Here is an executable code which works in Python 2.7 but results in an error in Python 3.6: import six class AMeta(type): def __new__(cls, name, bases, attrs): module

Solution 1:

What your problem is I can figure out, and how to work around it The problem is that when you do what you are doing, you are passing the samecell object to both copies of your class: the original and the legacy one.

As it exists in two classes at once, it conflicts with the other place it is in use when one tries to make use of it - super() will pick the wrong ancestor class when called.

cell objects are picky, they are created in native code, and can't be created or configured on the Python side. I could figure out a way of creating the class copy by having a method that will return a fresh cell object, and passing that as __classcell__.

(I also tried to simply run copy.copy/copy.deepcopy on the classcell object -before resorting to my cellfactory bellow - it does not work)

In order to reproduce the problem and figure out a solution I made a simpler version of your metaclass, Python3 only.

from types import FunctionType
legacies = []

defclasscellfactory():
    classM1(type):
        def__new__(mcls, name, bases, attrs, classcellcontainer=None):
            ifisinstance(classcellcontainer, list):
                classcellcontainer.append(attrs.get("__classcell__", None))

    container = []

    classT1(metaclass=M1, classcellcontainer=container):
        def__init__(self):
            super().__init__()
    return container[0]


defcellfactory():
    x = Nonedefhelper():
        nonlocal x
    return helper.__closure__[0]

classM(type):
    def__new__(mcls, name, bases, attrs):
        cls1 = super().__new__(mcls, name + "1", bases, attrs)
        new_attrs = attrs.copy()
        if"__classcell__"in new_attrs:
            new_attrs["__classcell__"] = cellclass = cellfactory()

            for name, obj in new_attrs.items():
                ifisinstance(obj, FunctionType) and obj.__closure__:
                    new_method = FunctionType(obj.__code__, obj.__globals__, obj.__name__, obj.__defaults__, (cellclass, ))
                    new_attrs[name] = new_method

        cls2 = super().__new__(mcls, name + "2", bases, new_attrs) 
        legacies.append(cls2)
        return cls1

classA(metaclass=M):
    defmeth(self):
        print("at A")

classB(A): 
    passclassC(B,A): 
    defmeth(self):
        super().meth()

C()

So, not only I create a nested-function in order to have the Python runtime create a separate cell object, that I then use in the cloned class - but also, methods that make use of the cellclass have to be re-created with a new __closure__ that points to the new cell var.

Without recreating the methods, they won't work in the clonned class - as super() in the cloned-class' methods will expect the cell pointing to the cloned class itself, but it points to the original one.

Fortunately, methods in Python 3 are plain functions - that makes the code simpler. However, that code won't run in Python 2 - so, just enclose it in an if block not to run on Python2. As the __cellclass__ attribute does not even exist there, there is no problem at all.

After pasting the code above in a Python shell I can run both methods and super() works:

In[142]: C().meth()                                                                                                                              
atAIn[143]: legacies[-1]().meth()                                                                                                                   
atA

Post a Comment for "__classcell__ Generates Error In Python 3.6 When The Metaclass Calls Multiple Super().__new__ From Inherited Class"