python学习笔记-tip42(定制类-内置方法的使用)
引言
我们在写java类时,通常会有toString() equals() compareTo()等等方法的重写,只要将这些方法重写,那么当我们调用类的对应方法时,类就会按照我们设定的方式去做出表现。
同样,python作为一个动态高级语言,他当然也是可以实现的。
前面我们在介绍获取对象信息的时候,我们使用到了dir()方法,这个方法可以获取对象的所有属性和方法,然后展示到一个字符串list里面去。
展示完之后,我们发现了很多__xxx__
类型的方法或者属性,这些类型的方法熟悉是python预留的,有特殊用途的方法和属性。
今天就给大家介绍几个功能和java中的toString()很类似的方法,这些方法或属性可以帮助我们“定制类”
__str__
它像极了toString()
我们定义一个类,然后打印他的类型时,他会打印很长的一段信息,如果我们想改变这个信息的话,我们需要重写__str__
方法
如下图所示:
通过学生类和教师类的对比,说明了
__str__
方法的用处
__iter__
方法可以让一个类可以被for...in...处理
class Fib(object):
def __init__(self):
self.a, self.b = 0, 1 # 初始化两个计数器a,b
def __iter__(self):
return self # 实例本身就是迭代对象,故返回自己
def __next__(self):
self.a, self.b = self.b, self.a + self.b # 计算下一个值
if self.a > 100000: # 退出循环的条件
raise StopIteration()
return self.a # 返回下一个值
调试结果
>>> for n in Fib():
... print(n)
...
1
1
2
3
5
...
46368
75025
这里不再介绍,后续需要再做讲解
__getitem__
方法可以通过索引取出元素
>>> Fib()[5]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'Fib' object does not support indexing
要表现得像list那样按照下标取出元素,需要实现__getitem__()
方法:
class Fib(object):
def __getitem__(self, n):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
现在,就可以按下标访问数列的任意一项了:
>>> f = Fib()
>>> f[0]
1
>>> f[1]
1
>>> f[2]
2
>>> f[3]
3
>>> f[10]
89
>>> f[100]
573147844013817084101
但是list有个神奇的切片方法:
>>> list(range(100))[5:10]
[5, 6, 7, 8, 9]
对于Fib却报错。原因是getitem()传入的参数可能是一个int,也可能是一个切片对象slice,所以要做判断:
class Fib(object):
def __getitem__(self, n):
if isinstance(n, int): # n是索引
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
if isinstance(n, slice): # n是切片
start = n.start
stop = n.stop
if start is None:
start = 0
a, b = 1, 1
L = []
for x in range(stop):
if x >= start:
L.append(a)
a, b = b, a + b
return L
现在试试Fib的切片:
>>> f = Fib()
>>> f[0:5]
[1, 1, 2, 3, 5]
>>> f[:10]
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
但是没有对step参数作处理:
>>> f[:10:2]
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
也没有对负数作处理,所以,要正确实现一个getitem()还是有很多工作要做的。
此外,如果把对象看成dict,getitem()的参数也可能是一个可以作key的object,例如str。
与之对应的是setitem()方法,把对象视作list或dict来对集合赋值。最后,还有一个delitem()方法,用于删除某个元素。
总之,通过上面的方法,我们自己定义的类表现得和Python自带的list、tuple、dict没什么区别,这完全归功于动态语言的“鸭子类型”,不需要强制继承某个接口。
下面着重说一下
__getattr__
方法
一般情况下,我们如果访问到类里一个没有被定义过的属性或者方法的时候,就会直接报错
我们解决的方法有两种
- 定义这个属性或者方法
- 重写
__getattr__
方法
第一种就不多说了,我们直接说下第二种
__getattr__
这个方法会在调用对象属性的时候直接被调用起来,所以可以通过重写该方法去解决问题
实现调用属性不报错
实现调用方法不报错
需要特别注意的是:
- 大家有没有注意到 我们有name的属性,所以直接通过实例对象调用name是可以访问到的,只有访问不到的属性或者方法才会调用
__getattr__
这个方法的 - 此外,如果使用了
__getattr__
那么如果在方法逻辑里面没有写返回值,那么调用一个不存在的方法或者属性的话,是默认返回None的,不会报错,如
不过如果我们需要报错的话,那么就需要修改__getattr__
方法,让他默认报错
总结一下__getattr__
他其实是帮助我们
「把一个类的所有属性和方法全部动态化处理了,不需要任何特殊手段」
那么我们就可以针对完全动态的情况做调用“
下面看一个重要例子,上面的内容都可以不看,但必须看这里哦
现在很多网站都搞REST api
比如新浪微博、豆瓣啥的,调用API的URL类似:
- http://api.server/user/friends
-
http://api.server/user/timeline/list
如果要写SDK,给每个URL对应的API都写一个方法,那得累死,而且,API一旦改动,SDK也要改。
利用完全动态的__getattr__
,我们可以写出一个链式调用:
若想实现逻辑,只需要在里面依次判断处理即可。
总结
这些python特意为我们提供的方法,可以让我们方便的将一个类构造成我们想要的样子
__getattr__
需要理解,其他的只需看懂即可。