Python中type和type.__new__有什么区别?

22

我正在编写一个元类,无意中却这样做了:

class MetaCls(type):
    def __new__(cls, name, bases, dict):
        return type(name, bases, dict)

...而不是像这样:

class MetaCls(type):
    def __new__(cls, name, bases, dict):
        return type.__new__(cls, name, bases, dict)

这两个元类之间究竟有什么区别?更具体地说,是什么导致第一个元类无法正常工作(一些类未被元类调用)?

6个回答

16

在第一个例子中,你正在创建一个全新的类:

>>> class MetaA(type):
...     def __new__(cls, name, bases, dct):
...         print 'MetaA.__new__'
...         return type(name, bases, dct)
...     def __init__(cls, name, bases, dct):
...         print 'MetaA.__init__'
... 
>>> class A(object):
...     __metaclass__ = MetaA
... 
MetaA.__new__
>>> 

在第二种情况下,你调用了父类的__new__方法:

>>> class MetaA(type):
...     def __new__(cls, name, bases, dct):
...         print 'MetaA.__new__'
...         return type.__new__(cls, name, bases, dct)
...     def __init__(cls, name, bases, dct):
...         print 'MetaA.__init__'
... 
>>> class A(object):
...     __metaclass__ = MetaA
... 
MetaA.__new__
MetaA.__init__
>>> 

1
对于Python3,class A(object, metaclass=MetaA): pass。在类A作用域中的__metaclass__完全被忽略。 - Mr_and_Mrs_D

9
你需要弄清楚的第一件事是object.__new__()如何工作。
以下是来自文档的内容:

object.__new__(cls[, ...])

用于创建类cls的新实例。__new__()是一个静态方法(特殊情况下不需要声明为这样),它将请求实例的类作为其第一个参数。其余的参数是传递给对象构造器表达式(调用类)的参数。 __new__()的返回值应该是新的对象实例(通常是cls的实例)。

典型的实现通过使用适当的参数调用超类的__new__()方法来创建类的新实例,例如:super(currentclass, cls).__new__(cls[, ...]),然后在返回之前根据需要修改新创建的实例。

如果__new__()返回cls的实例,则将调用新实例的__init__()方法,例如:__init__(self[, ...]),其中self是新实例,其余的参数与传递给__new__()的参数相同。

如果__new__()未返回cls的实例,则不会调用新实例的__init__()方法。

__new__()主要用于允许不可变类型(如intstrtuple)的子类自定义实例创建。它还经常在自定义元类中被覆盖,以自定义类创建。

因此,在mg.的答案中,前者不调用函数__init__,而后者在调用__new__之后调用函数__init__

2
type.__new__object.__new__ 有什么关系?我不太明白那部分。 - Nishant
OP 询问了 type.__new__(用于创建类),而不是 object.__new__(用于创建类的实例)。 - rlat
1
请查看关于 class type(object) 的文档 https://docs.python.org/3/library/functions.html?highlight=type#type ,我认为这可能是 type.new 和 object.new 之间的关系。如果你理解了,请记得取消你的踩票,谢谢! - polar9527
2
你们(@Nishant,@rlat)需要弄清楚的是Python的类型和对象是什么,答案在这里http://www.cs.utexas.edu/~cannata/cs345/Class%20Notes/15%20Python%20Types%20and%20Objects.pdf;因此,type.__new__与object.__new__相关之间没有区别。 - polar9527

8
请参考下面的注释,希望对您有所帮助。
class MetaCls(type):
    def __new__(cls, name, bases, dict):
        # return a new type named "name",this type has nothing
        # to do with MetaCls,and MetaCl.__init__ won't be invoked
        return type(name, bases, dict)

class MetaCls(type):
    def __new__(cls, name, bases, dict):
        # return a new type named "name",the returned type 
        # is an instance of cls,and cls here is "MetaCls", so 
        # the next step can invoke MetaCls.__init__ 
        return type.__new__(cls, name, bases, dict)

5
class MyMeta(type):
    def __new__(meta, cls, bases, attributes):
        print 'MyMeta.__new__'
        return type.__new__(meta, cls, bases, attributes)
    def __init__(clsobj, cls, bases, attributes):
        print 'MyMeta.__init__'

class MyClass(object):
  __metaclass__ = MyMeta
  foo = 'bar'

实现同样结果的另一种方式:

cls = "MyClass"
bases = ()
attributes = {'foo': 'bar'}
MyClass = MyMeta(cls, bases, attributes)

MyMeta是一个可调用对象,因此Python将使用特殊方法__call__

Python将在MyMeta的类型(在我们的情况下是type)中查找__call__

"对于新式类,只有在对象的类型中定义了特殊方法,而不是在对象的实例字典中定义的,才能保证隐式调用特殊方法正常工作"

MyClass = MyMeta(...)被解释为:

my_meta_type = type(MyMeta)
MyClass = my_meta_type.__call__(MyMeta, cls, bases, attributes)

type.__call__()内部,我想象的代码如下:

MyClass = MyMeta.__new__(MyMeta, cls, bases, attributes)
meta_class = MyClass.__metaclass__
meta_class.__init__(MyClass, cls, bases, attributes)
return MyClass

MyMeta.__new__()将决定如何构建MyClass:

type.__new__(meta, cls, bases, attributes)将为MyClass设置正确的元类(即MyMeta)。

type(cls, bases, attributes)将为MyClass设置默认的元类(即type)。


1
cls = "MyClass" 这个命名有些误导人,通常情况下 cls 表示类的实例。但是在这里你指的是类名。 - Izana

5
return type(name, bases, dict)

你从中得到的是一个新的“type”类型,而不是“MetaCls”的实例。因此,你在“MetaCls”中定义的方法(包括“__init__”)永远不会被调用。
当创建新类型时,“type.__new__”将被调用,但传入该函数的“cls”值将是“type”,而不是“MetaCls”。

2

这里详细描述了所有内容。

如果您没有返回正确类型的对象,则定义自定义元类就没有意义。


1
但这就是我要问的问题:为什么type不能返回正确类型的对象?我知道我必须使用子类的__new__方法,但这与使用type有何不同? - Jason Baker
1
类型应该如何神奇地知道您想要制作什么类型的对象? - Azeem.Butt
好的,因为我告诉它。我传入了名称、基础和字典。我还需要提供什么其他信息? - Jason Baker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接