学习第十四天

浏览: 196

定制类

写在前面:看到类似__slots__这样形如__xxx__的变量或者函数名就要注意,这些在python中是有特殊用途的。

__slots__上文已讲过如何运用,__len__()方法我们也知道是为了让class作用于len()函数。

除此之外,python的class中还有许多这样的特殊用途的函数,可以帮助我们定制类。

__str__

引子:我们先定义一个Student类,打印出一个实例:

image.png

但打印结果是这样的:image.png这样带一长串不好看,所以这时候我们来定定义__str__()方法,返回我们想要的它显示的结果。

image.png

但当我们直接赋值变量,不用print时,这时在打印出结果,

image.png

和第一次我们打印出的结果一样,这是因为直接显示的变量调用的不是__str__(),而是__repr__(),区别是__str__()返回用户看到的字符串,而__repr__()返回程序开发者看到的字符串,也就是说,__repr__()是为调试服务的。解决办法是再定义一个__repr__()。但是通常__str__()和__repr__()代码都是一样的,所以,有个偷懒的写法:

image.png


__iter__

如果一个类想被用于for......in.....循环,类似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象,然后,Python的for的循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。

举例:以斐波那契数列为例,写一个Fib类,可以作用于for循环:

image.png


作用于for循环:

image.png


__getitem__

Fib实例虽然可以作用于for循环,看起来和list有点像,但,把它当成list来使用还不行,因为他不能像list那样按照下标取出元素,需要实现__getitem__()方法:

image.png

就可以任意取数了。

但还是不能实现list的切片方法,这时因为__getitem__()传入的参数可能是一个int,也可能是一个切片对象slice,所以要判断:

image.png

现在就可以切片了

image.png

但还有一个问题就是不能对step参数处理

image.png

也不能对负数作处理,所以,要正确实现一个__getitem__()还是有很多工作要做的。

此外,如果把对象看成dict,__getitem__()的参数也可能是一个可以看做key的object,例如str.

与之对应的是 __setitem__()方法,把对象视作list或dict来对集合赋值,最后,还有一个__deIitem__()方法,用于删除某个元素。

总之,通过以上方法,我们可以定义的类表现的和Python自带的list、tuple、dict没什么区别,这完全归功于动态语言的“鸭子类型”,不需要强制继承某个接口。


__getattr__

正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错。当调用不存在的属性时就会出现AttributeError的错误,要避免这个错误,我们可以在加上这个没有的属性,除了这个方法外,我们还可以有另一个方法,那就是写一个__getattr__()方法,动态返回一个属性,示例如下:

image.png

返回函数也是可以的:

image.png

只是调用方式要变:

image.png

注意:只有在没有找到属性的情况下,才调用__getattr__,已有属性,不会在__getattr__中查找。此外,我们在任意调用时都会返回None,这是因为我们定义的__getattr__默认返回的就是None。要让class只相应特定的几个属性,我们就要按照约定,抛出AttributeError的错误:

image.png

运行下:

image.png

这实际上是把一个类的所有属性和方法调用都全部动态化处理了,不需要任何特殊手段。

作用:可以针对完全动态的情况做调用。

举例:现在很多网站都搞REST API, 比如新浪微博、豆瓣等,如果要写SDK,给每个URL对应的API都都写一个方法,那就累死了,API一旦改动,SDK就要改。利用完全动态的__getattr__就可以写出一个链式调用:

image.png

试试用print打印出来:

image.png

返回原理是这样的:


__call__

当我们在调用实例方法时,用instance.method()来调用。能不能直接在实例本身调用那,答案是可以的,只需要用__call__()方法。举例如下:

image.png

调用:

image.png

__call__()还可以定义参数。对实例进行直接调用就好比对一个函数调用,所以你完全可以把对象看成函数,把函数看成对象,因为两者本来就没有本质的区别。

如果把对象看成函数,那么函数本身是可以在运行期动态创建出来的,因为类的实例都是运行期创建出来的。这么一来,我们就模糊了对象和函数的界限。

那么。我们如何判断一个变量是对象还是函数,其实,我们只要判断他是否能被调用就可以了,能被调用的就是一个Callable()对象,通过callable()我们就可以判断一个对象是否能被调用。


总结:

完全动态调用特性:把一个类的所有属性和方法调用全部动态化处理 

 __call__(): 用于实例自身的调用,达到()调用的效果 , 即可以把此类的对象当作函数来使用,相当于重载了括号运算符  

__getattr__(): 当调用不存在的属性时调用此方法来尝试获得属性

链式运行过程:

image.png

分析

image.png

image.png

推荐 1
本文由 小白学Python 创作,采用 知识共享署名-相同方式共享 3.0 中国大陆许可协议 进行许可。
转载、引用前需联系作者,并署名作者且注明文章出处。
本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责。本站是一个个人学习交流的平台,并不用于任何商业目的,如果有任何问题,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。

2 个评论

还没回去呀
发完就回去了

要回复文章请先登录注册