类的特殊方法
Python 的 class 中还有许多这种形如__xxx__
的变量或者函数名,它们有特殊用途的函数,可以帮助我们定制类。
__len__
如果你调用 len()
函数试图获取一个对象的长度,实际上,在 len()
函数内部,它自动去调用该对象的__len__()
方法,所以,下面的代码是等价的:
>>> len('ABC')
3
>>> 'ABC'.__len__()
3
我们自己写的类,如果也想用 len(myObj)
的话,就自己写一个__len__()
方法:
>>> class Dog:
... def __len__(self):
... return 100
...
>>> dog = Dog()
>>> len(dog)
100
__str__
print一个实例时,自定义打印的字符串。
class Student:
def __str__(self):
return 'Student object'
s = Student()
print(s) #输出: Student object
但是在终端中直接显示变量不起作用,这是因为直接显示变量调用的不是__str__()
,而是__repr__()
。两者的区别是__str__()
返回用户看到的字符串,而__repr__()
返回程序开发者看到的字符串,也就是说, __repr__()
是为调试服务的。
>>> s
<__main__.Student object at 0x109afb310>
解决办法是再定义一个__repr__()
。但是通常__str__()
和__repr__()
代码都是一样的,所以,有个偷懒的写法:
class Student:
def __str__(self):
return 'Student object'
__repr__ = __str__
__iter__
如果一个类想被用于 for ... in
循环,类似 list 或 tuple 那样,就必须实现一个__iter__()
方法,该方法返回一个迭代对象(Iterator),然后, Python 的 for循环就会不断调用该迭代对象的__next__()
方法拿到循环的下一个值,直到遇到 StopIteration 错误时退出循环。
我们以斐波那契数列为例,写一个 Fib 类,可以作用于 for 循环:
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 > 100000: # 退出循环的条件
raise StopIteration();
return self.a # 返回下一个值
现在,试试把 Fib 实例作用于 for 循环:
>>> for n in Fib():
... print(n)
__next__
有些对象里包含对其他对象的引用:这些对象称为容器。Python中的容器,例如list等。
__next__
用于容器中的迭代,容器需要定义下面的方法以提供迭代支持:
Container.__iter__() #返回一个迭代器对象,对象要求支持后面介绍的迭代器协议。
迭代协议
迭代器对象被要求支持下面的两个方法,合起来形成迭代协议。
Iterator.__iter__() #返回迭代器对象自身。为了允许容器和迭代器被用于for和in语句中,必须实现该方法。
Iterator.__next__() #返回容器的下一个条目。如果没有更多的条目,抛出StopIteration异常。
手动迭代
内置函数iter,它会自动调用一个对象的__iter__
方法,并获得一个迭代器。
内置函数next,它会自动调用一个对象的__next__
方法,给定一个可迭代对象X,自身有__next__
方法,调用next(X)
等同于X.__next__()
,但前者简便很多。
列表等的内置对象,自身不是迭代器(没有实现__next__
方法),因为它们需要支持多次迭代。对于这样的对象,我们必须调用iter来启动迭代。
>>> L = [1,2,3]
>>> I = iter(L)
>>> I.__next__()
1
>>> next(I)
2
__getitem__
Fib 实例虽然能作用于 for 循环,看起来和 list 有点像,但是,把它当成list 来使用还是不行,比如,取第 5 个元素:
>>> Fib()[5]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'Fib' object does not support indexing
要表现得像 list 那样按照下标取出元素,需要实现__getitem__()
方法:
def __getitem__(self, index):
a, b = 1, 1
for i in range(index):
a, b = b, a + b
return a
现在,就可以按下标访问数列的任意一项了:
>>> f = Fib()
>>> f[0]
1
但是 list还有一个切片方法,__getitem__()
传入的参数可能是一个 int,也可能是一个切片对象 slice,所以要做判断。还需要处理step、负数下标等等。要正确实现一个__getitem__()
还是有很多工作要做的。
最后,与__getitem__
对应的是__setitem__()
方法,把对象视作 list 或 dict 一样来对集合赋值。还有一个__delitem__()
方法,用于删除某个元素。
总之,通过上面的方法,我们自己定义的类表现得和 Python 自带的 list、tuple、 dict 没什么区别,这完全归功于动态语言的“鸭子类型”,不需要强制继承某个接口。
__getattr__
在访问实例的属性的时候,如果该属性不存在则会自动调用__getattr__
来继续查找。
要让class只响应特定的几个属性,我们就要按照约定,抛出 AttributeError 的错误。
class Student:
def __getattr__(self, attr):
if attr == 'age':
return lambda:25
raise AttributeError("Student's object has no attribute %s" % attr)
这实际上可以把一个类的所有属性和方法调用全部动态化处理了,不需要任何特殊的手段。
class Gen:
def __init__(self, root_url=''):
self.url = root_url
def __getattr__(self, item):
if item == 'get' or item == 'post':
return self.url
return Gen('{}/{}'.format(self.url, item))
gen = Gen('http://')
s = gen.users.show.erick.com.get #链式调用
print(s) #输出:http://users.show.erick.com
充分利用__getattr__
会在没有查找到相应实例属性时会被调用的特点,方便的通过链式调用生成对应的url。
__call__
当我们调用实例方法时,我们通常用 实例变量.方法()
形式来调用。
如果定义一个__call__()
方法,就可以直接对实例进行调用。
class Student(object):
def __init__(self, name):
self.name = name
def __call__(self): #注:__call__()还可以定义参数。
print('My name is %s.' % self.name)
调用方式如下:
>>> s = Student('Michael')
>>> s() # self 参数不要传入
My name is Michael.
这么一来,我们就模糊了对象和函数的界限。
那么,怎么判断一个变量是对象还是函数呢?其实,更多的时候,我们需要判断一个对象是否能被调用,能被调用的对象就是一个 Callable 对象,比如函数和我们上面定义的带有__call__()
的类实例:
>>> callable(Student())
True
>>> callable(max)
True
>>> callable([1, 2, 3])
False
>>> callable(None)
False
>>> callable('str')
False
通过 callable()函数,我们就可以判断一个对象是否是“可调用”对象。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论