定制类
写在前面:看到类似__slots__这样形如__xxx__的变量或者函数名就要注意,这些在python中是有特殊用途的。
__slots__上文已讲过如何运用,__len__()方法我们也知道是为了让class作用于len()函数。
除此之外,python的class中还有许多这样的特殊用途的函数,可以帮助我们定制类。
__str__
引子:我们先定义一个Student类,打印出一个实例:
但打印结果是这样的:这样带一长串不好看,所以这时候我们来定定义__str__()方法,返回我们想要的它显示的结果。
但当我们直接赋值变量,不用print时,这时在打印出结果,
和第一次我们打印出的结果一样,这是因为直接显示的变量调用的不是__str__(),而是__repr__(),区别是__str__()返回用户看到的字符串,而__repr__()返回程序开发者看到的字符串,也就是说,__repr__()是为调试服务的。解决办法是再定义一个__repr__()。但是通常__str__()和__repr__()代码都是一样的,所以,有个偷懒的写法:
__iter__
如果一个类想被用于for......in.....循环,类似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象,然后,Python的for的循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。
举例:以斐波那契数列为例,写一个Fib类,可以作用于for循环:
作用于for循环:
__getitem__
Fib实例虽然可以作用于for循环,看起来和list有点像,但,把它当成list来使用还不行,因为他不能像list那样按照下标取出元素,需要实现__getitem__()方法:
就可以任意取数了。
但还是不能实现list的切片方法,这时因为__getitem__()传入的参数可能是一个int,也可能是一个切片对象slice,所以要判断:
现在就可以切片了
但还有一个问题就是不能对step参数处理
也不能对负数作处理,所以,要正确实现一个__getitem__()还是有很多工作要做的。
此外,如果把对象看成dict,__getitem__()的参数也可能是一个可以看做key的object,例如str.
与之对应的是 __setitem__()方法,把对象视作list或dict来对集合赋值,最后,还有一个__deIitem__()方法,用于删除某个元素。
总之,通过以上方法,我们可以定义的类表现的和Python自带的list、tuple、dict没什么区别,这完全归功于动态语言的“鸭子类型”,不需要强制继承某个接口。
__getattr__
正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错。当调用不存在的属性时就会出现AttributeError的错误,要避免这个错误,我们可以在加上这个没有的属性,除了这个方法外,我们还可以有另一个方法,那就是写一个__getattr__()方法,动态返回一个属性,示例如下:
返回函数也是可以的:
只是调用方式要变:
注意:只有在没有找到属性的情况下,才调用__getattr__,已有属性,不会在__getattr__中查找。此外,我们在任意调用时都会返回None,这是因为我们定义的__getattr__默认返回的就是None。要让class只相应特定的几个属性,我们就要按照约定,抛出AttributeError的错误:
运行下:
这实际上是把一个类的所有属性和方法调用都全部动态化处理了,不需要任何特殊手段。
作用:可以针对完全动态的情况做调用。
举例:现在很多网站都搞REST API, 比如新浪微博、豆瓣等,如果要写SDK,给每个URL对应的API都都写一个方法,那就累死了,API一旦改动,SDK就要改。利用完全动态的__getattr__就可以写出一个链式调用:
试试用print打印出来:
返回原理是这样的:
__call__
当我们在调用实例方法时,用instance.method()来调用。能不能直接在实例本身调用那,答案是可以的,只需要用__call__()方法。举例如下:
调用:
__call__()还可以定义参数。对实例进行直接调用就好比对一个函数调用,所以你完全可以把对象看成函数,把函数看成对象,因为两者本来就没有本质的区别。
如果把对象看成函数,那么函数本身是可以在运行期动态创建出来的,因为类的实例都是运行期创建出来的。这么一来,我们就模糊了对象和函数的界限。
那么。我们如何判断一个变量是对象还是函数,其实,我们只要判断他是否能被调用就可以了,能被调用的就是一个Callable()对象,通过callable()我们就可以判断一个对象是否能被调用。
总结:
完全动态调用特性:把一个类的所有属性和方法调用全部动态化处理
__call__(): 用于实例自身的调用,达到()调用的效果 , 即可以把此类的对象当作函数来使用,相当于重载了括号运算符
__getattr__(): 当调用不存在的属性时调用此方法来尝试获得属性
链式运行过程:
分析