本文始发于我的公众号:TechFlow,原创不易,求个关注web
今天这篇是Python专题的第17篇文章,咱们来聊聊Python当中一个新的默认函数__new__。面试
上一篇当中咱们讲了如何使用type函数来动态建立Python当中的类,除了type能够完成这一点以外,还有另一种用法叫作metaclass。本来这一篇应该是继续元类的内容,讲解metaclass的使用。可是metaclass当中用到了一个新的默认函数__new__,关于这个函数你们可能会比较陌生,因此在咱们研究metaclass以前,咱们先来看看__new__这个函数的用法。设计模式
若是你去面试Python工程师的岗位,面试官问你,请问Python当中的类的构造函数是什么?并发
你不假思索,固然是__init__啦!若是你这么回答,颇有可能你就和offer无缘了。由于在Python当中__init__并非构造函数,__new__才是。是否是有点蒙,多西得(日语:为何)?咱们不是一直将__init__方法当作构造函数来用的吗?怎么又冒出来一个__new__,若是__new__才是构造函数,那么为何咱们建立类的时候历来不用它呢?编辑器
别着急,咱们慢慢来看。首先咱们回顾一下__init__的用法,咱们随便写一段代码:函数
class Student:
def __init__(self, name, gender):
self.name = name
self.gender = gender
复制代码
咱们一直都是这么用的,对不对,毫无问题。可是咱们换一个问题,咱们在Python当中怎么实现单例(Singleton)的设计模式呢?怎么样实现工厂呢?url
从这个问题出发,你会发现只使用__init__函数是不可能完成的,由于__init__并非构造函数,它只是初始化方法。也就是说在调用__init__以前,咱们的实例就已经被建立好了,__init__只是为这个实例赋上了一些值。若是咱们把建立实例的过程比喻成作一个蛋糕,__init__方法并非烘焙蛋糕的,只是点缀蛋糕的。那么显然,在点缀以前必须先烘焙出一个蛋糕来才行,那么这个烘焙蛋糕的函数就是__new__。spa
咱们来看下__new__这个函数的定义,咱们在使用Python面向对象的时候,通常都不会重构这个函数,而是使用Python提供的默认构造函数,Python默认构造函数的逻辑大概是这样的:线程
def __new__(cls, *args, **kwargs):
return super().__new__(cls, *args, **kwargs)
复制代码
从代码能够看得出来,函数当中基本上什么也没作,就原封不动地调用了父类的构造函数。这里隐藏着Python当中类的建立逻辑,是根据继承关系一级一级建立的。根据逻辑关系,咱们能够知道,当咱们建立一个实例的时候,其实是先调用的__new__函数建立实例,而后再调用__init__对实例进行的初始化。咱们能够简单作个实验:设计
class Test:
def __new__(cls):
print('__new__')
return object().__new__(cls)
def __init__(self):
print('__init__')
复制代码
当咱们建立Test这个类的时候,经过输出的顺序就能够知道Python内部的调用顺序。
从结果上来看,和咱们的推测彻底同样。
那么咱们重载__new__函数能够作什么呢?通常都是用来完成__init__没法完成的事情,好比前面说的单例模式,经过__new__函数就能够实现。咱们来简单实现一下:
class SingletonObject:
def __new__(cls, *args, **kwargs):
if not hasattr(SingletonObject, "_instance"):
SingletonObject._instance = object.__new__(cls)
return SingletonObject._instance
def __init__(self):
pass
复制代码
固然,若是是在并发场景当中使用,还须要加上线程锁防止并发问题,但逻辑是同样的。
除了能够实现一些功能以外,还能够控制实例的建立。由于Python当中是先调用的__new__再调用的__init__,因此若是当调用__new__的时候返回了None,那么最后获得的结果也是None。经过这个特性,咱们能够控制类的建立。好比设置条件,只有在知足条件的时候才能正确建立实例,不然会返回一个None。
好比咱们想要建立一个类,它是一个int,可是不能为0值,咱们就能够利用__new__的这个特性来实现:
class NonZero(int):
def __new__(cls, value):
return super().__new__(cls, value) if value != 0 else None
复制代码
那么当咱们用0值来建立它的时候就会获得一个None,而不是一个实例。
理解了__new__函数的特性以后,咱们就能够灵活运用了。咱们能够用它来实现许多其余的设计模式,好比大名鼎鼎常用的工厂模式。
所谓的工厂模式是指经过一个接口,根据参数的取值来建立不一样的实例。建立过程的逻辑对外封闭,用户没必要关系实现的逻辑。就比如一个工厂能够生产多种零件,用户并不关心生产的过程,只须要告知须要零件的种类。也所以称为工厂模式。
好比说咱们来建立一系列游戏的类:
class Last_of_us:
def play(self):
print('the Last Of Us is really funny')
class Uncharted:
def play(self):
print('the Uncharted is really funny')
class PSGame:
def play(self):
print('PS has many games')
复制代码
而后这个时候咱们但愿能够经过一个接口根据参数的不一样返回不一样的游戏,若是不经过__new__,这段逻辑就只能写成函数而不能经过面向对象来实现。经过重载__new__咱们就能够很方便地用参数来获取不一样类的实例:
class GameFactory:
games = {'last_of_us': Last_Of_us, 'uncharted': Uncharted}
def __new__(cls, name):
if name in cls.games:
return cls.games[name]()
else:
return PSGame()
uncharted = GameFactory('uncharted')
last_of_us = GameFactory('last_of_us')
复制代码
相信看到这里,关于__new__这个函数的用法应该都能理解了。通常状况下咱们是用不到这个函数的,只会在一些特殊的场景下使用。虽然如此,咱们学会它并不仅是用来实现设计模式,更重要的是能够加深咱们对于Python面向对象的理解。
除此以外,另外一个常用__new__场景是元类。因此今天的这篇文章其实也是为了后面介绍元类的其余用法打基础。
若是喜欢本文,能够的话,请点个关注,给我一点鼓励,也方便获取更多文章。