看到类似__slots__这种形如__xxx__的变量或者函数名就要注意,这些在Python中是有特殊用途的。
slots__我们已经知道怎么用了,len()方法我们也知道是为了能让class作用于len()函数。
除此之外,Python的class中还有许多这样有特殊用途的函数,可以帮助我们定制类。
str()、iter()、getitem()
直接显示变量调用的不是__str(),而是__repr__(),两者的区别是__str__()返回用户看到的字符串,而__repr__()返回程序开发者看到的字符串,也就是说,repr()是为调试服务的。采用__str__()能够让显示出来的格式更好看。
如果一个类想被用于for … in循环,类似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。
下例为斐波那契数列。
class Student(object):
def __init__(self,name):
self.name = name
def __str__(self):
return 'Student object (name: %s)' % self.name
__repr__ = __str__
class Fib(object):
def __init__(self):
self.a , self.b = 0,1
def __iter__(self):
return self
def __next__(self):
self.a,self.b = self.b,self.a + self.b
if self.a > 10000:
raise StopIteration()
return self.a
def main():
print(Student('Jhon'))
for n in Fib():
print(n)
if __name__ == '__main__':
main()
运行结果
Student object (name: Jhon)
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765
要表现得像list那样按照下标取出元素,需要实现__getitem__()方法:
list有一个切片方法:
print(list(range(100))[5:10])
[5, 6, 7, 8, 9]
但是对于Fib2却报错,原因是__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
>>> f = Fib()
>>> f[0:5]
[1, 1, 2, 3, 5]
>>> f[:10]
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错。
要避免这个错误,除了可以加上一个score属性外,Python还有另一个机制,那就是写一个__getattr__()方法,动态返回一个属性。修改如下:
class Student(object):
def __init__(self):
self.name = 'Michael'
def __getattr__(self, attr):
if attr=='score':
return 99
此外,注意到任意调用如s.abc都会返回None,这是因为我们定义的__getattr__默认返回就是None。要让class只响应特定的几个属性,我们就要按照约定,抛出AttributeError的错误:
class Student(object):
def __getattr__(self, attr):
if attr=='age':
return lambda: 25
raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)
任何类,只需要定义一个__call__()方法,就可以直接对实例进行调用。请看示例:
call()还可以定义参数。对实例进行直接调用就好比对一个函数进行调用一样,所以你完全可以把对象看成函数,把函数看成对象,因为这两者之间本来就没啥根本的区别。
如果你把对象看成函数,那么函数本身其实也可以在运行期动态创建出来,因为类的实例都是运行期创建出来的,这么一来,我们就模糊了对象和函数的界限。
那么,怎么判断一个变量是对象还是函数呢?其实,更多的时候,我们需要判断一个对象是否能被调用,能被调用的对象就是一个Callable对象,比如函数和我们上面定义的带有__call__()的类实例:
>>> callable(Student())
True
>>> callable(max)
True
>>> callable([1, 2, 3])
False
>>> callable(None)
False
>>> callable('str')
False