Python札记5:__init__函数和__new__函数

目前仍有较多的初学者,或者从其他语言转Python的朋友认为__init__函数就是Python中类的构造函数,其实是不对的。

Python 2的早期,确实是没有__new__函数的,但那是很多年前的事情了,现在的Python 2和Python 3中,类实例的创建过程均遵循先调用__new__函数构造类实例,然后调用__init__函数对实例进行初始化。

先new再init

看一个简单的例子:

class Sample(object):

  def __new__(cls):
    print("Sample.__new__ called")
    return super().__new__(cls)
  
  def __init__(self):
    print("Sample.__init__ called")

s = Sample()

上面的代码会输出:

Sample.__new__ called
Sample.__init__ called

即Python解释器会先调用__new__函数再调用__init__函数。

无new不init

上面代码中__new__函数中调用了父类(object类)的__new__函数,父类的__new__函数会创建并返回一个cls类,也就是Sample类的实例对象。Sample类的__new__函数继续返回这个对象。接着这个实例对象被传入__init__函数,用于初始化该对象中的一些成员。过程如下图所示:

在这里插入图片描述

注意:__new__是静态函数,__init__是实例函数。

如果,__new__函数不返回实例对象,那么__init__函数就不会被调用:

class Sample(object):

  def __new__(cls):
    print("Sample.__new__ called")
    # return super().__new__(cls)
  
  def __init__(self):
    print("Sample.__init__ called")

s = Sample()
print(s)

上面的代码会输出:

Sample.__new__ called
None

也就是说,__new__函数不返回实例对象,那就无法创建类实例。

自定义__new__函数的返回值

如果我们把代码改成这样:

class Sample(object):

  def __new__(cls):
    print("Sample.__new__ called")
    return 666
  
  def __init__(self):
    print("Sample.__init__ called")

s = Sample()
print(s)

输出:

Sample.__new__ called
666

可以看到,__new__函数决定了我们最终创建的是什么类型的对象。这里Sample.__init__函数没有被调用,因为666是一个int类型的实例,调用的是int.__init__函数,这个函数不会输出任何信息。当然,我们还可以返回其他类型的对象,所以__new__函数给我们带来了很多灵活性。

另外还有一点需要注意,Python规定__init__函数只能返回None,否则会引起TypeError

class Sample(object):

  def __new__(cls):
    print("Sample.__new__ called")
    return super().__new__(cls)
  
  def __init__(self):
    print("Sample.__init__ called")
    return 1

s = Sample()

输出:

Sample.__new__ called
Sample.__init__ called
Traceback (most recent call last):
  File "main.py", line 11, in <module>
    s = Sample()
TypeError: __init__() should return None, not 'int'

单例模式(Singleton)

重写__new__函数可以带给我们很多灵活性,例如实现单例模式,例如在游戏中同一时刻只允许出现一个大Boss,只有Boss被打死之后,才能新召唤一个Boss:

class Boss(object):
  _instance = None

  def __new__(cls):
    print("Sample.__new__ called")
    if cls._instance is None:
      cls._instance = super().__init__(cls)
    return cls._instance
  
  def __init__(self):
    print("Sample.__init__ called")

boss1 = Boss()
boss2 = Boss()

print(id(boss1)) # 140620000188688
print(id(boss2)) # 140620000188688

可以看到boss1和boss2都指向同一个实例。

工厂模式(Factory)

上面我们看到,我们可以自定义__new__函数的返回值,那么我们可以使用这个特性,实现工厂模式。同样以游戏为例,我们需要一个兵工厂来给我们生产各种类型的战士,例如地精、萨满祭司等等:

class Soldier(object):

  def __init__(self):
    pass

  def attack(self):
    pass
  
  def retreat(self):
    print("Retreat!!!")


class Goblin(Soldier):
  """
  地精
  """
  def __init__(self):
    pass

  def attack(self):
    print("Goblins are attacking!!!")

class Shaman(Soldier):
  """
  萨满
  """
  def __init__(self):
    pass

  def attack(self):
    print("Shamans are attacking!!!")


class SoldierFactory(object):
  # 兵工厂能够生产的战士类型
  soldiers = {'goblin': Goblin, 'shaman': Shaman}

  def __new__(cls, name):
    if name in cls.soldiers.keys():
      return cls.soldiers[name]()
    return None


goblin1 = SoldierFactory("goblin")
goblin2 = SoldierFactory("goblin")
shaman1 = SoldierFactory("shaman")
shaman2 = SoldierFactory("shaman")

goblin1.attack()  # Goblins are attacking!!!
goblin2.attack()  # Goblins are attacking!!!
shaman1.attack()  # Shamans are attacking!!!
shaman2.attack()  # Shamans are attacking!!!

goblin1.retreat() # Retreat!!!
goblin2.retreat() # Retreat!!!
shaman1.retreat() # Retreat!!!
shaman2.retreat() # Retreat!!!

以上就是关于始化函数__init__和构造函数__new__的一些简介。


我的知乎:奔三的鑫鑫

欢迎关注微信公众号:小鑫的代码日常

欢迎加入Python学习交流群:532232743,这里有各路高手等着你~

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页