>>> 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所示。
工厂模式的实现代码如下:
>>> 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__()
不是。