本文始发于个人公众号: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. kafka消费者重试逻辑的实现

    背景 在kafka的消费者中,如果消费某条消息出错,会导致该条消息不会被ack,该消息会被不断的重试,阻塞该分区的其他消息的消费,因此,为了保证消息队列不被阻塞,在出现异常的情况下,我们一般还是会ac ...

  2. node的fs模块

    node的file system模块提供的api有同步和异步两种模式(大多数情况下都是用的异步方法,毕竟异步是node的特色,至于提供同步方法,可能应用程序复杂的时候有些场景使用同步会比较合适).异步 ...

  3. Java并发编程详解读书笔记(一)

    一.线程介绍 讲线程之前得先了解进程(Peocess),现在的操作系统基本都支持多任务的进行,举个场景:有许多的程序员们喜欢边coding边听点轻音乐.这时计算机就是做并行任务,也就是有多个进程在同时 ...

  4. hdu4746莫比乌斯反演进阶题

    Mophues Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 327670/327670 K (Java/Others)Total S ...

  5. 六、表达式:前缀&&后缀

    count为运算后的值.

  6. html5学习之路_006

    表单与php交互 <!DOCTYPE html> <html lang="en"> <head> <meta charset=" ...

  7. 缓冲区(Buffer)的数据存取

    缓冲区(Buffer) 1. 缓冲区(Buffer):一个用于特定基本数据类 型的容器. 由 java.nio 包定义的,所有缓冲区 都是 Buffer 抽象类的子类.2. Java NIO 中的 B ...

  8. jchdl - RTL实例 - Adder

    https://mp.weixin.qq.com/s/9S29BCTcJfbpR62ALjSidA   加法器.   参考链接 https://github.com/wjcdx/jchdl/blob/ ...

  9. Java实现 蓝桥杯 算法训练 字串统计

    算法训练 字串统计 时间限制:1.0s 内存限制:512.0MB 问题描述 给定一个长度为n的字符串S,还有一个数字L,统计长度大于等于L的出现次数最多的子串(不同的出现可以相交),如果有多个,输出最 ...

  10. Java实现 POJ 2749 分解因数(计蒜客)

    POJ 2749 分解因数(计蒜客) Description 给出一个正整数a,要求分解成若干个正整数的乘积,即a = a1 * a2 * a3 * - * an,并且1 < a1 <= ...