代码改变世界

深入理解python__new__和__init__

2017-05-26 14:44  核桃不是桃  阅读(221)  评论(0)    收藏  举报

  先看题目:如何派生内置不可变类型并修改实例化行为?

题目:自定义一种新类型的元组,对于传入的可迭代对象,只保留作为其中int类型并且值大于0的元素,例如intTuple([1,-1,'abc',6,['x','y'],3])=>(1,6,3)

要求intTuple是tuple的子类,如何实现?

  • 1 class IntTuple(tuple): 2 def __init__(self, iterable): 3 #before 4 super(IntTuple, self).__init__(iterable) 5 #after 6 7 t = IntTuple([1,-1,'abc',6,['x','y'],3]) 8 print t

第一行定义了一个名字叫做IntTuple的类,继承内置不可变类型tuple,第二行定义构造方法,在构造方法中,调用父类的构造方法,并返回一个可迭代对象。没有任何修改。

现在我们在构造方法中尝试过滤掉无关的参数。方法是在调用父类构造器之前和之后,分别测试。

首先看在调用父类构造器之后能否改变?答案是否定的,因为self是一个tuple的实例,而tuple是一个不可变类型,因此这种方法不成立。

现在尝试在调用父类构造器之前,通过改变参数iterable来看能否达到需求。

1 class IntTuple(tuple):
2     def __init__(self, iterable):
3         #before
4         super(IntTuple, self).__init__((1,6,3))
5         #after
6 
7 t = IntTuple([1,-1,'abc',6,['x','y'],3])
8 print t

这里我们手动将其改为一个(1,6,3),然后看一下返回结构:

(1, -1, 'abc', 6, ['x', 'y'], 3)

运行之后发现结构并不是预想的,分析可知,在__init__之前,是__new__这个静态方法创建了self这个参数。

 1 # -*-endcoding:utf8-*-
 2 class IntTuple(tuple):
 3     def __new__(cls, iterable):
 4         g = (x for x in iterable if isinstance(x, int) and x > 0)
 5         # 调用父类的__new__方法:
 6         return super(IntTuple, cls).__new__(cls, g)
 7 
 8     def __init__(self, iterable):
 9         #before
10         super(IntTuple, self).__init__(iterable)
11         #after
12 
13 t = IntTuple([1,-1,'abc',6,['x','y'],3])
14 print t

第三行,在调用__init__之前调用了__new__方法,关于__new__有下面这些知识点:

__new__只有继承自object的新式类才有

__new__至少要有一个参数cls,表示要实例化的类,此参数在实例化时有python解释器自动提供

__new__必须要有返回值,返回实例化出来的实例,这点在自己实现new的时候要特别注意。

__init__有一个参数self,就是这个__new__返回的实例,__init__在__new__的基础上可以完成一些其它初始化的动作,__init__不需要返回值

若__new__没有正确返回当前类cls的实例,那__init__是不会被调用的,即使是父类的实例也不行

理解了这些,再看运行结果:

1 (1, 6, 3)

结果却是如我们所料。

我们总结__new__和__init__的区别

  • __new__是一个静态方法,__init__是一个实例方法
  • __new__方法会返回一个创建的实例,而__init__接受了这个返回值,且什么都不返回
  • 只有在__new__返回一个cls的实例时后面的__init__才能被调用
  • 当创建一个新实例时调用__new__初始化一个实例时用__init__