本文始发于个人公众号:TechFlow,原创不易,求个关注

今天这篇是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)的设计模式呢?怎么样实现工厂呢?

从这个问题出发,你会发现只使用__init__函数是不可能完成的,因为__init__并不是构造函数,它只是初始化方法。也就是说在调用__init__之前,我们的实例就已经被创建好了,__init__只是为这个实例赋上了一些值。如果我们把创建实例的过程比喻成做一个蛋糕,__init__方法并不是烘焙蛋糕的,只是点缀蛋糕的。那么显然,在点缀之前必须先烘焙出一个蛋糕来才行,那么这个烘焙蛋糕的函数就是__new__。

__new__函数

我们来看下__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__场景是元类。所以今天的这篇文章其实也是为了后面介绍元类的其他用法打基础。

如果喜欢本文,可以的话,请点个关注,给我一点鼓励,也方便获取更多文章。

Python面试进阶问题,__init__和__new__的区别是什么?的更多相关文章

  1. 简述 Python 类中的 __init__、__new__、__call__ 方法

    任何事物都有一个从创建,被使用,再到消亡的过程,在程序语言面向对象编程模型中,对象也有相似的命运:创建.初始化.使用.垃圾回收,不同的阶段由不同的方法(角色)负责执行. 定义一个类时,大家用得最多的就 ...

  2. python类中的__init__和__new__方法

    Python中类: Python中在创建类的过程中最先调用的不是__init__方法而是__new__方法,__new__方法是一个静态方法,在创建一个类对象时其实是通过__new__方法首先创建出一 ...

  3. python中的__init__ 、__new__、__call__小结

    这篇文章主要介绍了python中的__init__ .__new__.__call__小结,需要的朋友可以参考下 1.__new__(cls, *args, **kwargs)  创建对象时调用,返回 ...

  4. 详解python中的__init__与__new__方法

    一.__init__和__new__方法执行的顺序? 在面向对象中介绍了关于对象创建的过程,我们知道__new__方法先于__init__方法执行. 二.__new__方法是什么? 首先,我们先来看下 ...

  5. Python中的__init__和__new__

    一.__init__ 方法是什么? 使用Python写过面向对象的代码的同学,可能对 __init__ 方法已经非常熟悉了,__init__ 方法通常用在初始化一个类实例的时候.例如: # -*- c ...

  6. python 的__init__ 和__new__ 区别

    在此介绍一下  __init__ 和  __new__ 先后调用的区别 代码如下: # __init__ 和 __new__的区别 # 通常在编代码时,__init__ 较为常见,但是__new__却 ...

  7. python中的__init__和__new__的区别

    一.__init__ 方法是什么?(init前后的线是双下划线) 使用Python写过面向对象的代码的同学,可能对 __init__ 方法已经非常熟悉了,__init__ 方法通常用在初始化一个类实例 ...

  8. Python基础(十) __init__与__new__区别

    __init__与__new__区别: __init__在python,其实是,在实例化之后执行的,用来初始化一些属性,相当于构造函数,但是又不一样 细心一些,通过参数会有所发现,其实__init__ ...

  9. Python入门之面向对象的__init__和__new__方法

    Python入门之面向对象的__init__和__new__方法

随机推荐

  1. MyBatis In的使用

    http://blog.csdn.net/unei66/article/details/17792503 MyBatis In的使用 标签: mybatisin 2014-01-03 16:23 74 ...

  2. iOS [AFHTTPSessionManager GET:parameters:progress:success:failure:]: unrecognized selector sent to

    AFN更新到4.0.1后,崩溃[AFHTTPSessionManager GET:parameters:progress:success:failure:]: unrecognized selecto ...

  3. 必会技能!Docker助你快速上手玩转HBase!

    前言:本文主要讲述了如何使用Docker快速上手HBase,省去繁杂的安装部署环境,直接上手,小白必备.适合HBase入门学习及简单代码测试. 1. Docker 安装 参考地址: https://y ...

  4. elasticsearch7.X x-pack破解

    简介: x-pack是elasticsearch的一个收费的扩展包,将权限管理,警告,监视等功能捆绑在一个易于安装的软件包中,x-pack被设计为一个无缝的工作,但是你可以轻松的启用或者关闭一些功能. ...

  5. 把数据写入txt中 open函数中 a与w的区别

    a: 打开一个文件用于追加.如果该文件已存在,文件指针将会放在文件的结尾. 也就是说,新的内容将会被写入到已有内容之后.如果该文件不存在,创建新文件进行写入. w:  打开一个文件只用于写入.如果该文 ...

  6. mysql事务控制和锁定语句

    MySQL 支持对 MyISAM 和 MEMORY 存储引擎的表进行表级锁定,对 BDB 存储引擎的表进行页级锁定,对 InnoDB 存储引擎的表进行行级锁定.默认情况下,表锁和行锁都是自动获得的,不 ...

  7. 格雷码 CSP(NOIP??)2019 洛谷 P5657

    洛谷AC通道! 多年过后,重新来看这道D1T1,20min不到AC,再回忆起当初考场三小时的抓耳挠腮,不禁感慨万千啊!! 发篇题解记录一下. 思路:直接dfs模拟即可(二进制找规律是不可能的, 这辈子 ...

  8. [自动化-脚本]001.自动领淘金币:Anyweb模拟操作

    通过模拟手工操作的方法领取淘金币.该方法万能且通用,有能力的还可以自行修改脚本. 工具 软件下载 anywebscript.com 方法/步骤 1.安装软件如图所示 2.设置脚本: (1)进入网站:[ ...

  9. 画出决策边界线--plot_2d_separator.py源代码【来自python机器学习基础教程】

    import numpy as np import matplotlib.pyplot as plt from .plot_helpers import cm2, cm3, discrete_scat ...

  10. TCP 半连接队列和全连接队列满了会发生什么?又该如何应对?

    前言 网上许多博客针对增大 TCP 半连接队列和全连接队列的方式如下: 增大 TCP 半连接队列的方式是增大 /proc/sys/net/ipv4/tcp_max_syn_backlog: 增大 TC ...