Base metaclass overriding __new__ generates classe

2019-02-28 20:30发布

问题:

I found a weird behavior with cpython 2.5, 2.7, 3.2 and pypy with metaclass that override __new__ when using the python 2 / python 3 compatible way of using metaclass :

Given a module m1:

class C1Meta(type):
    def __new__(cls, name, bases, dct):
        return type.__new__(cls, name, bases, dct)

C1 = C1Meta('C1', (object,), {})


class C2Meta(type):
    pass

C2 = C2Meta('C2', (object,), {})

And the following main program:

import m1

C10 = m1.C1Meta('C10', (m1.C1,), {})


class C11Meta(m1.C1Meta):
    pass

C11 = C11Meta('C11', (m1.C1,), {})


class C12Meta(m1.C1Meta):
    def __new__(cls, name, bases, dct):
        return m1.C1Meta.__new__(cls, name, bases, dct)

C12 = C12Meta('C12', (m1.C1,), {})


class C13Meta(m1.C1Meta):
    def __new__(cls, name, bases, dct):
        return type.__new__(cls, name, bases, dct)

C13 = C13Meta('C13', (m1.C1,), {})


C20 = m1.C2Meta('C20', (m1.C2,), {})


class C21Meta(m1.C2Meta):
    pass

C21 = C21Meta('C21', (m1.C2,), {})


class C22Meta(m1.C2Meta):
    def __new__(cls, name, bases, dct):
        return m1.C2Meta.__new__(cls, name, bases, dct)

C22 = C22Meta('C22', (m1.C2,), {})


class C23Meta(m1.C2Meta):
    def __new__(cls, name, bases, dct):
        return type.__new__(cls, name, bases, dct)

C23 = C23Meta('C23', (m1.C2,), {})


print(C10)
print(C11)
print(C12)
print(C13)

print(C20)
print(C21)
print(C22)
print(C23)

Running the script will produce the following output (with all the mentioned python versions) :

<class 'm1.C10'>
<class 'm1.C11'>
<class 'm1.C12'>
<class '__main__.C13'>
<class '__main__.C20'>
<class '__main__.C21'>
<class '__main__.C22'>
<class '__main__.C23'>

-> the C10, C11 and C12 classes module is wrong !

Is it an expected behavior ?

Is there a way to override new that will not raise the issue ?

Thanks,

Christophe

回答1:

Apparently the __module__ attribute is set when the metaclass executes. In your case, the metaclass executes inside m1. With a normally defined class, the __module__ attribute is automatically generated and passed to the metaclass. However, you are creating your classes with a manual call to the metaclass, and passing in an empty attribute dictionary. Thus your classes do not provide a __module__ attribute. The __module__ attribute is created automatically, but not until type.__new__ is called. Since this happens inside module m1, the __module__ created at that time refers to m1.

I'm not exactly sure why you're calling the metaclass explicitly. The whole point of metaclasses is to allow you to customize what happens when you use a class statement to define a class. If you don't want to use a class statement, you can just use regular functions to create your classes, and don't need to bother with a metaclass at all.

That said, I believe you can get your desired behavior, by doing, e.g.,

C11 = C11Meta('C11', (m1.C1,), {'__module__': __name__})