>>> from env_helper import info; info()
页面更新时间: 2023-12-27 10:37:51
运行环境:
    Linux发行版本: Debian GNU/Linux 12 (bookworm)
    操作系统内核: Linux-6.1.0-16-amd64-x86_64-with-glibc2.36
    Python版本: 3.11.2

6.2. __init__() 不是构造方法

很多Pythoner会有这样的误解,认为 __init__() 方法是类的构造方法。 因为从表面上看 它确实很像构造方法:当需要实例化一个对象的时候, 使用 a=Class(args...) 便可以返回一个类的实例,其中 args 的参数与方法中申明的参数一样。 可是事实真相是怎样的呢?我们通过例子说明。

>>> class A(object):
>>>     def __new__(cls, *args,**kwargs):
>>>         print (cls)
>>>         print (args )
>>>         print (kwargs )
>>>         print ("--------------")
>>>         instance = object.__new__(cls, *args,**kwargs)
>>>         print (instance)
>>>     def __init__(self,a,b):
>>>         print ("init gets called" )
>>>         print ("self is", self )
>>>         self.a,self.b = a,b
>>> al=A(1,2)
>>> print (al.a)
>>> print (al.b)
<class '__main__.A'>
(1, 2)
{}
--------------
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-1-83788fb8d949> in <module>()
     11         print ("self is", self )
     12         self.a,self.b = a,b
---> 13 al=A(1,2)
     14 print (al.a)
     15 print (al.b)


<ipython-input-1-83788fb8d949> in __new__(cls, *args, **kwargs)
      5         print (kwargs )
      6         print ("--------------")
----> 7         instance = object.__new__(cls, *args,**kwargs)
      8         print (instance)
      9     def __init__(self,a,b):


TypeError: object.__new__() takes exactly one argument (the type to instantiate)

我们原本期望的是能够正确输出a和b的值,可是运行却抛出了异常。 除了异常外还有 来自对__new__()方法调用所产生的输出,可是我们明明没有直接调用__new__()方法,原 因在哪里? 实际上并不是真正意义上的构造方法,方法所做的工作是 在类的对象创建好之后进行变量的初始化。 __new__()方法才会真正创建实例,是类的构造 方法。 这两个方法都是object类中默认的方法,继承自object的新式类,如果不覆盖这两 个方法将会默认调用object中对应的方法。 上面的程序抛出异常是因__new__()方法中并 没有显式返回对象, 因此实际上a1为None, 当去访问实例属性a时抛出 AttributeError: 'NoneType' object has no attribute 'a' 的错误也就不难理解了。

我们来看着 __new__() 方法和 __init__() 方法的定义。

  • object.__new__( cls [,args...]):其中 els 代表类t args 为参数列表。

  • object.__init__( self [,args...]):其中self代表实例对象,args为参数列表。

这两个方法之间有些不同点,总结如下:

  • 根据 Python 文梢(http://dacs.pythmi.org/2/refereiice/datamodel.htm/#object.__new__ ) 可知,__new__()方法是静态方法,而__init__()为实例方法。

  • __new__()方法一般需要返回类的对象,当返回类的对象吋将会自动调用__init__()方 法进行初始化,如果没有对象返回,则__init__()方法不会被调用。方法不需要显式返回,默认为None,否则会在运行时拋出TypeError。

  • 当需要控制实例创建的时候可使用__new__()方法,而控制实例初始化的时候使用__init__() 方法。

  • 一般情况下不需要覆盖__new__()方法,但当子类继承自不可变类型.如str、int、 unicode或者tuple的时候,往往需要覆盖该方法。

  • 当需要植盖__new__()__init__()方法的时候这两个方法的参数必须保持一致,如 果不一致将导致异常。示例如下:

>>> class Test(object):
>>>     def __new__(cls, x):
>>>           return super(Test,cls) .__new__(cls)
>>>     def __init__(self,x,y):
>>>         self.x=x
>>>         self.y = y
>>> Test(1)
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-3-da2f972909a9> in <module>()
      5         self.x=x
      6         self.y = y
----> 7 Test(1)


TypeError: __init__() missing 1 required positional argument: 'y'
>>> Test(2,3)
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-4-7785d4b015fe> in <module>()
----> 1 Test(2,3)


TypeError: __new__() takes 2 positional arguments but 3 were given

前面我们提到,一般情况下掼盖方法就能满足大部分需求,那么在什么特殊 情况下需要覆__init__()方法呢?有以下几种情况:

1)当类继承(如str、int、unicode、tuple或者forzenset等)不可变类型且默认的__new__()方法不能满足需求的时候。来看一个例子:假设我们需要一个不可修改的集合,该集合能 够将任何以空格隔开的字符串变为集合中的元素。现在不覆盖__new__()方法,仅覆盖__init__()方法看看是否可行。

>>> class UserSet(frozenset):
>>>     def __init__(self, arg=None):
>>>         if isinstance(arg, str):
>>>             arg = arg.split()
>>>         frozenset.__init__(arg)
>>>
>>> print (UserSet("I am testing "))
>>> print (frozenset("I am testing "))
UserSet({'g', 'I', 't', ' ', 'n', 'i', 'a', 'e', 'm', 's'})
frozenset({'g', 'I', 't', ' ', 'n', 'i', 'a', 'e', 'm', 's'})

上面的输出显然没有满足用户的需求,用户希望得到的输出是

UserSet(['I', 'frozen', 'set', 'am', 'testing'])

实际上这些不可变类型的 __init__() 方法是个伪方法,必须重新覆盖 __new__() 方法才能满足需求。方法实现的代码如下:

>>> def __new__(cls,*args):
>>>     if args and isinstance (args[0],basestring):
>>>         args = (args[0].split(),) + args[l:]
>>>     return super (UserSet.cls).__new__(cls, *args)

2)用来实现工厂模式或者申•例模式或者进行元类编程(元类编程中常常需要使用__new__()来控制对象创建。这部分内容会在建议62中进行阐述)的时候。以简单工厂为例子, 它由一个工厂类根据传入的参M决定创建出哪一种产品类的实例,属于类的创建型模式。其 类的关系如图6-2所示。

_images/img2.png

工厂模式的实现代码如下:

>>> class Shape(object):
>>>     def __init__(object):
>>>         pass
>>>     def draw(self):
>>>         pass
>>> class Triangle(Shape):
>>>     def __init__(self):
>>>         print ("I am a triangle")
>>>     def draw(self):
>>>         print ("I am drawing triangle")
>>> class Rectangle(Shape):
>>>     def __init__(self):
>>>         print ("I am a rectnagle")
>>>     def draw(self):
>>>         print ("I am drawing triangle1")
>>> class Trapezoid(Shape):
>>>     def __init__(self):
>>>         print (" I am a trapezoid")
>>>     def draw(self):
>>>         print ("I am drawing triangle")
>>> class Diamond(Shape):
>>>     def __init__(self):
>>>         print("I am a diamond")
>>>     def draw(self):
>>>         print ("I am drawing triangle")
>>> class ShapeFactory(object):
>>>     shapes = {'triangle': Triangle, 'rectangle': Rectangle,'trapezoid': Trapezoid, 'diamond': Diamond}
>>>     def __new__(klass,name):
>>>         if name in ShapeFactory.shapes.keys():
>>>             print ("creating a new shape %s" % name)
>>>             return ShapeFactory.shapes[name]()
>>>         else:
>>>             print ("creating a new shape %s" % name)
>>>             return Shape()

在ShapeFactory类中重新概盖了__new__()方法,外界通过调用该方法来创建其所霜 的对象类型,但如果所请求的类是系统所不支持的,则返回Shape对象。在引入了工厂类之 后.只需要使用如下形式就可以创建不同的图形对象:

>>> ShapeFactory('rectangle').draw()
creating a new shape rectangle
I am a rectnagle
I am drawing triangle1

3)作为用来初始化的__init__()方法在多继承的情况下,子类的__init__()方法如果不 显式调用父类的__init__()方法,则父类的__init__()方法不会被调用。

>>> class A(object):
>>>     def __init__(self):
>>>         print ("I am A's  __init__")
>>> class B(A):
>>>     def __init__(self):
>>>         print ("I am B's  __init__")
>>> b = B()
I am B's  __init__

程序输出为:I am B's  __init__。父类入的__init__()方法并没有被调用,所以要初始化 父类中的变量需要在子类的__init__()方法中使用super ( B,self).__init()。对于多继承的情 况,我们可以通过迭代子类的__bases__属性中的内容来逐一调用父类的初始化方法。

注意:__new__()方法才是类的构造方法,而__init__()不是。