【转载】Python中__new__和__init__的区别

声明:本文参考的文章较多,在头部仅填写一个链接,在具体内容中,将参考来源及链接贴出。

1. 引言

首先简要介绍一下__init____new__的区别:

这部分参考: 作者:Dan 来源: stackoverflow
原文链接:https://stackoverflow.com/questions/674304/why-is-init-always-called-after-new

  • 我们使用__new__来获得一个类的实例,而使用__init__是来初始化一个实例
  • __new__是第一步,用来创建实例,并将该实例返回;__init__不返回任何东西,它的主要作用是在实例被创建之后对其进行初始化。
  • 通常情况下,我们不要重写__new__方法,除非你想继承一个不可变的类型,如str, int, unicode 或者 tuple。
  • 当你要重写__new__方法时,你可以使用工厂来代替。

下面我们分别详细介绍__init____new__的区别、以及Python工厂

2. __new__和__init__的区别

这部分参考:作者:耀凯考前突击大师 来源: 简书
原文链接:https://www.jianshu.com/p/14b8ebf93b73

2.1 摘要

本文讨论了Python中__init____new__方法。

__init____new__具有不同的功能。并且对于Python的新类和旧类而言功能也不同。

2.2 __new__和__init__功能上的区别

__init____new__的主要区别在于:__new__是用来创造一个类的实例的(constructor),而__init__是用来初始化一个实例的(initializer)。

2.3 Python的新类和旧类

Python中的类分为新类和旧类。旧类是Python3之前的类,旧类并不是默认继承object类,而是继承type类。

Python2中的旧类如下面代码所示:

class oldStyleClass: # inherits from 'type'
    pass

Python2中定义一个新类:

class newStyleClass(object): # explicitly inherits from 'object'
    pass

在Python3中所有的类均默认继承object,所以并不需要显式地指定object为基类。

以object为基类可以使得所定义的类具有新类所对应的方法(methods)和属性(properties)。

在下面的文章中我们会分别基于新类和旧类探讨___init____new__

2.3.1 __init__和__new__参数的不同

__new__所接收的第一个参数是cls,而__init__所接收的第一个参数是self。这是因为当我们调用__new__的时候,该类的实例还并不存在(也就是self所引用的对象还不存在),所以需要接收一个类作为参数,从而产生一个实例。而当我们调用__init__的时候,实例已经存在,因此__init__接受self作为第一个参数并对该实例进行必要的初始化操作。这也意味着__init__是在__new__之后被调用的。

2.3.2 Python旧类中的__new__和__init__

Python的旧类中实际上并没有__new__方法。因为旧类中的__init__实际上起构造器的作用。所以如果我们定义如下旧类:

class oldStyleClass:
    def __new__(cls):
        print("__new__ is called") # this line will never get called during construction

print oldStyleClass()

程序输出结果如下:

<__main__.oldStyleClass instance at 0x7f0c9d7c77e8>

可见创建及初始化对象的过程并没有调用__new__。实际上,除非显式调用:oldStyleClass.__new__( ),该类中的__new__方法中的内容永远不会被调用。因为旧类构造实例并不会调用__new__方法。

但如果我们重载__init__方法:

class oldStyleClass:
    def __init__(self):
        print("__init__ is called")

print oldStyleClass()

结果是:

__init__ is called
<__main__.oldStyleClass instance at 0x7f83a9b447e8>

如果我们在__init__中加上return语句,将会导致TypeError: __init__() should return None的错误。

class oldStyleClass:
    def __init__(self):
        return 29

oldStyleClass()

程序结果如下:

in __init__()
Traceback (most recent call last):
  File "/ttttt.py", line 17, in <module>
    print oldStyleClass()
TypeError: __init__() should return None
2.3.3 Python新类中的__new__和__init__

Python的新类允许用户重载__new____init__方法,且这两个方法具有不同的作用。__new__作为构造器,起创建一个类实例的作用。而__init__作为初始化器,起初始化一个已被创建的实例的作用。

如下面代码是所示:

class newStyleClass(object): 
    # In Python2, we need to specify the object as the base.
    # In Python3 it's default.

    def __new__(cls):
        print("__new__ is called")
        return super(newStyleClass, cls).__new__(cls)

    def __init__(self):
        print("__init__ is called")
        print("self is: ", self)

newStyleClass()

结果如下:

__new__ is called
__init__ is called
('self is: ', <__main__.newStyleClass object at 0x7f29b0aa61d0>)
<__main__.newStyleClass object at 0x7f29b0aa61d0>

创建类实例并初始化的过程中__new____init__被调用的顺序也能从上面代码的输出结果中看出:__new__函数首先被调用,构造了一个newStyleClass的实例,接着__init__函数在__new__函数返回一个实例的时候被调用,并且这个实例作为self参数被传入了__init__函数。

这里需要注意的是,如果__new__函数返回一个已经存在的实例(不论是哪个类的),__init__不会被调用。如下面代码所示:

obj = 12 
# obj can be an object from any class, even object.__new__(object)

class returnExistedObj(object):
    def __new__(cls):
        print("__new__ is called")
        return obj

    def __init(self):
        print("__init__ is called")

returnExistedObj()

执行结果如下:

__new__ is called
12

同时另一个需要注意的点是:

如果我们在__new__函数中不返回任何对象,则__init__函数也不会被调用。

如下面代码所示:

class notReturnObj(object):
    def __new__(cls):
        print("__new__ is called")

    def __init__(self):
        print("__init__ is called")

print(notReturnObj())

结果如下:

__new__ is called
None

可见如果__new__函数不返回对象的话,不会有任何对象被创建,__init__函数也不会被调用来初始化对象。

2.4 总结几个点
  • __init__不能有返回值
  • __new__函数直接上可以返回别的类的实例。如上面例子中的returnExistedObj类的__new__函数返回了一个int值。
  • 只有在__new__返回一个新创建属于该类的实例时当前类的__init__才会被调用。如下面例子所示:
class sample(object):
    def __str__(self):
        print("sample")


class example(object):
    def __new__(cls):
        print("__new__ is called")
        return sample()

    def __init__(self):
        print("__init__ is called")


e = example()
e.__str__()

输出结果为:

__new__ is called
sample
3. Python工厂模式

这部分参考: 作者: 夏秋, 来源:segmentfault
原文链接:https://segmentfault.com/a/1190000013053013

3.1 前言

工厂模式,顾名思义就是我们可以通过一个指定的“工厂”获得需要的“产品”,在设计模式中主要用于抽象对象的创建过程,让用户可以指定自己想要的对象而不必关心对象的实例化过程。这样做的好处是用户只需通过固定的接口而不是直接去调用类的实例化方法来获得一个对象的实例,隐藏了实例创建过程的复杂度,解耦了生产实例和使用实例的代码,降低了维护的复杂性。

工厂模式一般包含简单工厂、工厂方法和抽象工厂三种模式。

3.2简单工厂

首先,我们先看一个简单工厂的例子:

class Mercedes(object):
    """梅赛德斯
    """
    def __repr__(self):
        return "Mercedes-Benz"

class BMW(object):
    """宝马
    """
    def __repr__(self):
        return "BMW"

假设我们有两个“产品”分别是Mercedes和BMW的汽车,如果没有“工厂”来生产它们,我们就要在代码中自己进行实例化,如:

mercedes = Mercedes()
bmw = BMW()

但现实中,你可能会面对很多汽车产品,而且每个产品的构造参数还不一样,这样在创建实例时会遇到麻烦。这时就可以构造一个“简单工厂”把所有汽车实例化的过程封装在里面。

class SimpleCarFactory(object):
    """简单工厂
    """
    @staticmethod
    def product_car(name):
        if name == 'mb':
            return Mercedes()
        elif name == 'bmw':
            return BMW()

有了SimpleCarFactory类后,就可以通过向固定的接口传入参数获得想要的对象实例,如下:

c1 = SimpleCarFactory.product_car('mb')
c2 = SimpleCarFactory.product_car('bmw')
3.3 工厂方法

虽然有了一个简单的工厂,但在实际使用工厂的过程中,我们会发现新问题:如果我们要新增一个“产品”,例如Audi的汽车,我们除了新增一个Audi类外还要修改SimpleCarFactory内的product_car方法。这样就违背了软件设计中的开闭原则,即在扩展新的类时,尽量不要修改原有代码。所以我们在简单工厂的基础上把SimpleCarFactory抽象成不同的工厂,每个工厂对应生成自己的产品,这就是工厂方法。

#coding=utf-8
import abc

class AbstractFactory(object):
    """抽象工厂
    """
    __metaclass__ = abc.ABCMeta

    @abc.abstractmethod
    def product_car(self):
        pass

class MercedesFactory(AbstractFactory):
    """梅赛德斯工厂
    """
    def product_car(self):
        return Mercedes()

class BMWFactory(AbstractFactory):
    """宝马工厂
    """
    def product_car(self):
        return BMW()

我们把工厂抽象出来用abc模块实现了一个抽象的基类AbstractFactory,这样就可以通过特定的工厂来获得特定的产品实例了:

c1 = MercedesFactory().product_car()
c2 = BMWFactory().product_car()

每个工厂负责生产自己的产品也避免了我们在新增产品时需要修改工厂的代码,而只要增加相应的工厂即可。如新增一个Audi产品,只需新增一个Audi类和AudiFactory类。

3.4 抽象工厂

工厂方法虽然解决了我们“修改代码”的问题,但如果我们要生产很多产品,就会发现我们同样需要写很多对应的工厂类。比如如果MercedesFactory和BMWFactory不仅生产小汽车,还要生产SUV,那我们用工厂方法就要再多构造两个生产SUV的工厂类。所以为了解决这个问题,我们就要再更进一步的抽象工厂类,让一个工厂可以生产同一类的多个产品,这就是抽象工厂。具体实现如下:

#coding=utf-8
import abc

# 两种小汽车
class Mercedes_C63(object):
    """梅赛德斯 C63
    """
    def __repr__(self):
        return "Mercedes-Benz: C63"

class BMW_M3(object):
    """宝马 M3
    """
    def __repr__(self):
        return "BMW: M3"

# 两种SUV
class Mercedes_G63(object):
    """梅赛德斯 G63
    """
    def __repr__(self):
        return "Mercedes-Benz: G63"

class BMW_X5(object):
    """宝马 X5
    """
    def __repr__(self):
        return "BMW: X5"

class AbstractFactory(object):
    """抽象工厂
    可以生产小汽车外,还可以生产SUV
    """
    __metaclass__ = abc.ABCMeta

    @abc.abstractmethod
    def product_car(self):
        pass

    @abc.abstractmethod
    def product_suv(self):
        pass

class MercedesFactory(AbstractFactory):
    """梅赛德斯工厂
    """
    def product_car(self):
        return Mercedes_C63()

    def product_suv(self):
        return Mercedes_G63()

class BMWFactory(AbstractFactory):
    """宝马工厂
    """
    def product_car(self):
        return BMW_M3()

    def product_suv(self):
        return BMW_X5()

我们让基类AbstractFactory同时可以生产汽车和SUV,然后令MercedesFactory和BMWFactory继承AbstractFactory并重写product_car和product_suv方法即可。

c1 = MercedesFactory().product_car()
s1 = MercedesFactory().product_suv()
print(c1, s1)  # Mercedes-Benz: C63 Mercedes-Benz: G63
s2 = BMWFactory().product_suv()
c2 = BMWFactory().product_car()
print(c2, s2)  # BMW: M3 BMW: X5

抽象工厂模式与工厂方法模式最大的区别在于,抽象工厂中的一个工厂对象可以负责多个不同产品对象的创建 ,这样比工厂方法模式更为简单、有效率。

3.5 结论

初学设计模式时会对三种工厂模式的实际应用比较困惑,其实三种模式各有优缺点,应用的场景也不尽相同:

  • 简单工厂模式适用于需创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂的情况下,而且用户只关心那种类型的实例被创建,并不关心其初始化过程时,比如多种数据库(MySQL/MongoDB)的实例,多种格式文件的解析器(XML/JSON)等。

  • 工厂方法模式继承了简单工厂模式的优点又有所改进,其不再通过一个工厂类来负责所有产品的创建,而是将具体创建工作交给相应的子类去做,这使得工厂方法模式可以允许系统能够更高效的扩展。实际应用中可以用来实现系统的日志系统等,比如具体的程序运行日志,网络日志,数据库日志等都可以用具体的工厂类来创建。

  • 抽象工厂模式在工厂方法基础上扩展了工厂对多个产品创建的支持,更适合一些大型系统,比如系统中有多于一个的产品族,且这些产品族类的产品需实现同样的接口,像很多软件系统界面中不同主题下不同的按钮、文本框、字体等等。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值