Python __new__ 和 __init__ 的区别

由于知乎的编辑器不支持 MarkDown 语法, 文章中的一些 MarkDown 标注无法正确显示, 如果你想要追求更好的阅读体验, 可以移步该博客的简书链接.

Python __new__ 和 __init__ 的区别​www.jianshu.com

`__new__()` 是在新式类中新出现的方法,它作用在构造方法( `__init__()` )建造实例之前. 可以这么理解,在 Python 中存在于类里面的构造方法 `__init__()` 负责将类进行实例化,而在 `__init__()` 启动之前,`__new__()` 决定是否要使用该 `__init__()` 方法,因为 `__new__()` 也可以调用其他类的构造方法或者直接返回别的对象来作为本类的实例。

如果将类比喻为工厂,那么 `__init__()` 方法则是该工厂的生产工人,`__init__()` 方法接受的初始化参数则是生产所需原料,`__init__()` 方法会按照方法中的语句负责将原料加工成实例以供工厂出货。而 `__new__()` 则是生产部经理,`__new__()` 方法可以决定是否将原料提供给该生产部工人,同时它还决定着出货产品是否为该生产部的产品,因为这名经理可以借该工厂的名义向客户出售完全不是该工厂的产品。

`__new__()` 方法的特性:

  • `__new__()` 方法是在类准备将自身实例化时调用。
  • `__new__()` 方法始终都是类的静态方法,即使没有被加上静态方法装饰器。

类的实例化和构造方法通常是这个样子:

class MyClass(object):
	def __new__(cls, *args, **kwargs):
    	return object.__new__(cls, *args, **kwargs)
    def __init__(self, *args, **kwargs):
        ...
# 实例化
myclass = MyClass(*args, **kwargs)

正如以上代码所示,一个类可以有多个位置参数和多个命名参数,而在实例化开始之后,在调用 `__init__()` 方法之前,Python 首先调用 `__new__()` 方法.

`__new__` 的参数解释:

* `cls` 表示当前类.

* `*args` 和 `**kwargs` 分别表示该类进行初始化时, 输入的位置参数和命名参数。

`__new__` 函数一般会有返回语句, 在返回语句中: `object.__new__()` 表示调用 `object` 类的 `__new__()` 函数, 也可以使用 `super().__new__()`, 表示调用当前类的父类的 `__new__()`, 如果父类没有自定义 `__new__()`, 就会调用该父类的父类的 `__new__()`, 以此类推, 直到 `object` 类. `return` 中的 `__new__()` 函数的第一个参数 `cls` 表示将要返回的类的类型, `cls` 表示用于返回当前类, 也可以返回其他类.

事实上如果(新式)类中没有重写 `__new__()` 方法,即在定义新式类时没有重新定义 `__new__()` 时,Python 默认是调用该类的直接父类的 `__new__()` 方法来构造该类的实例,如果该类的父类也没有重写 `__new__()`,那么将一直按此规则追溯至 `object` 类的 `__new__()` 方法,因为 `object` 类是所有新式类的基类。

如果新式类中重写了 `__new__()` 方法,那么你可以自由选择任意一个的其他的新式类(必定要是新式类,只有新式类必定都有 `__new__()`,因为所有新式类都是 `object` 的后代,而经典类则没有 `__new__()` 方法)的 `__new__()` 方法来制造实例,包括这个新式类的所有前代类和后代类,只要它们不会造成递归死循环。具体看以下代码解释:

class Foo(object):
    def __init__(self, *args, **kwargs):
        ...
    def __new__(cls, *args, **kwargs):
        return object.__new__(cls, *args, **kwargs)    

class Child(Foo):
    def __new__(cls, *args, **kwargs):
        return super().__new__(cls, *args, **kwargs)
class Stranger(object):
	def __new__(cls, *args, **kwargs):
		...

* 如果Child中没有定义 `__new__()` 方法,那么会自动调用其父类的 `__new__()` 方法来制造实例,即 `Foo.__new__(cls, *args, **kwargs)`。

* 在任何新式类的 `__new__()` 方法,不能调用自身的 `__new__()` 来制造实例,因为这会造成死循环。因此必须避免类似以下的写法:

1. 在 `Foo` 中避免:`return Foo.__new__(cls, *args, **kwargs)` 或 `return cls.__new__(cls, *args, **kwargs)`。`Child` 同理。

1. 使用 `object` 或者没有血缘关系的新式类的 `__new__()` 是安全的,但是如果是在有继承关系的两个类之间,应避免互调造成死循环,例如: `(Foo) return Child.__new__(cls)` , `(Child) return Foo.__new__(cls)`。

* 在制造 `Stranger` 实例时,会自动调用 `object.__new__(cls)`.

通常来说,新式类开始实例化时,`__new__()` 方法会返回 `cls`( `cls` 指代当前类)的实例,然后该类的 `__init__()` 方法作为构造方法会接收这个实例(即`self`)作为自己的第一个参数,然后依次传入 `__new__()` 方法中接收的位置参数和命名参数。

`__new__()` 除了返回 `cls` (当前类) 的实例之外,还可以有其他用法:

  • 返回其他类:
class Foo(object):
    def __new__(cls, *args, **kwargs):
        return object.__new__(Stranger, *args, **kwargs) 
    def __init__(self, *args, **kwargs):
        ... 

class Stranger(object):
    ...

foo = Foo()
print(type(foo))    

打印的结果显示 `foo` 其实是 `Stranger` 类的实例。

  • 返回其他数据类型:
a = 10
class Foo(object):
    def __new__(cls, *args, **kwargs):
        print("__new__ is called.")
        return a
    def __init__(self, *args, **kwargs):
        ...
Foo()

执行结果为:

__new__ is called
10

可以这么描述 `__new__()` 和 `__ini__()` 的区别,在新式类中 `__new__()` 才是真正的实例化方法,为类提供外壳制造出实例框架,然后调用该框架内的构造方法 `__init__()` 使其丰满。

如果以建房子做比喻,`__new__()` 方法负责开发地皮,打下地基,并将原料存放在工地。而 `__init__()` 方法负责从工地取材料建造出地皮开发招标书中规定的大楼,`__init__()` 负责大楼的细节设计,建造,装修使其可交付给客户。

注意:如果 `__new__()` 没有返回 `cls`(即当前类)的实例,那么当前类的 `__init__()` 方法是不会被调用的。如果 `__new__()` 返回其他类(新式类或经典类均可)的实例,那么只会调用被返回的那个类的构造方法。

猜你喜欢

转载自blog.csdn.net/w17688977481/article/details/88619173