高级编程技巧 学习笔记
一、如何派生内置不可变类型并修改其实例化行为
1.1、问题
我们想自定义一种新类型的元组,对于传入的可迭代对象,我们只保留其中int类型且值大于0的元素,例如:IntTuple([2,-2,'jr',['x','y'],4]) => (2,4)
如何继承内置 tuple 实现 IntTuple ?
1.2、解决
- 创建 IntTuple 类继承 tuple ,通过 for 循环实现
class IntTuple(tuple):
def __init__(self, iterable):
for i in iterable:
if isinstance(i, int) and i > 0:
super().__init__(i)
int_t = IntTuple([2, -2, 'li', ['x', 'y'], 4])
print(int_t)
# 报错 TypeError: object.__init__() takes exactly one argument (the instance to initialize)
有问题,object 类出错,说明代码存在问题,打印 self 看看
class IntTuple(tuple):
def __init__(self, iterable):
# for i in iterable:
# if isinstance(i, int) and i > 0:
# super().__init__(i)
print(self)
int_t = IntTuple([2, -2, 'li', ['x', 'y'], 4])
print(int_t)
# 输出
(2, -2, 'li', ['x', 'y'], 4)
(2, -2, 'li', ['x', 'y'], 4)
输出的 self 是一个元组对象,说明在执行 __init__
方法之前就已经创建好 self 对象
所以,上面的报错是代码顺序存在问题
- 创建对象的是
__new__
方法
__new__
方法的第一个参数是这个类,而其余的参数会在调用成功后全部传递给__init__方法初始化,所以,__new__
方法(第一个执行)先于 __init__
方法执行;若没有返回值,则 __init__
方法也不会执行:
class A(object):
def __new__(cls, *args, **kwargs):
print("A.__new__", cls, args)
# return super.__new__(cls) # 取消注释观察
# return object.__new__(cls)
def __init__(self, *args):
print("A.__init__")
a = A(1,2)
# 是如何执行的?(下面两句相当于上面一句)
a = A.__new__(A, 1, 2)
A.__init__(a, 1, 2)
我们比较两个方法的参数,可以发现__new__
方法是传入类 (cls),而__init__
方法传入类的实例化对象 (self),而有意思的是,__new__
方法返回的值就是一个实例化对象(如果__new__
方法返回 None,则__init__
方法不会被执行,并且 返回值只能调用父类中的__new__
方法,而不能调用毫无关系的类的__new__
方法 )。
- 对代码进行修改
class IntTuple(tuple):
def __new__(cls, iterable):
# 生成器
f = (i for i in iterable if isinstance(i, int) and i>0)
return super().__new__(cls, f) # cls 其实就是 IntTuple , 因为类名可能会变, 所以用 cls 代替
int_t = IntTuple([2, -2, 'li', ['x', 'y'], 4])
print(int_t)