前言
最近在阅读flask
源码的时候,经常看到一些python
类中,类似于__xxx__
的变量和
函数名,这些大多是在python
中有特殊用途的。Python
的class
中有许多类似于这样
的函数,我们用他们来定制我们的类,来实现一些class
本身不具有的方法。由于有些方
法我也不是很明白,为了能更好的理解他们,索性就做一个总结。参考谷歌的博客及官方
文档
先简单定义一个类:
class Me(object):
def __init__(self, name):
self.name = name
__call__
在Python
中,调用函数可以直接使用abs(123)
来调用,而调用一个类的实例方法时,
都是使用instance.method()
来调用。那如果我们想像调用函数那样调用实例,就需要
__call__()
方法了。以上面的类为例:
...
def __call__(self, age):
print('{name}\'s age is {age}.'.format(name=self.name,age=age))
直接调用它:
>>> me = Me('GuTianle')
>>> me(20)
GuTianle's age is 20.
其实在Python
中,对象和函数的界限是很模糊的,函数可以动态创建出来,当然类的实
例对象也是动态创建出来的,如果类中有__call__()
方法,那其实对象和函数没有根本
的区别。
如果有一个变量a = 1
,我们如何判断实例me
和a
是对象或者函数还是变量呢?答案
是使用callable()
函数,一个可被调用的对象就是一个Callable
对象:
>>> callable(me) # Me类的实例
True
>>> callable(a) # 变量 a=1
False
>>> callable(abs) # 求绝对值的内置函数
True
__repr__
__repr__()
方法是比较常用的方法,通常用于调试,表示对象的主要信息。依然是上面
的例子:
>>> me = Me('GuTianle')
>>> me
<__main__.Me object at 0x0000000002A01E48>
返回的结果显然不是我们想要的,如果能直接打印出实例的信息就好了,解决方法是定义
__repr__()
方法,让他返回一个我们需要的信息。
...
def __repr__(self):
return '<Me object {name}>'.format(name=self.name)
接着打印实例:
>>> me = Me('GuTianle')
>>> me
<Me object GuTianle>
__getattr__
仍然以上面的类为例,当我们调用他的sex
属性时,由于他并不存在,就会报错:
>>> me = Me('GuTianle')
>>> print(me.name)
GuTianle
>>> print(me.sex)
Traceback (most recent call last):
...
AttributeError: 'Me' object has no attribute 'sex'
通常我们调用一个类的属性和方法时,如果调用的不存在,就会报错。这种情况,我们只
要给他加一个sex
属性就好了。但是,有些情况我们并不需要sex
属性,但是当调用
sex
属性的属性的时候可以给他返回结果。这就需要定义一个__getattr__()
方法来动
态返回一个属性:
...
def __getattr__(self, attr):
if attr == 'name':
return 'Zhazhahui'
if attr == 'sex':
if self.name == 'GuTianle':
return 'male'
return 'female'
现在再调用他:
>>> print(me.sex)
'male'
>>> print(me.name)
'GuTianle'
>>> print(me.height)
None
但是如果我们在__getattr__()
方法下,在判断if attr == 'name':...
,当我们调用
name
属性时,程序首先在类中查找是否有name
属性,如果有,程序是不会执行
__getattr__()
下的判断的。
此外,从上面的调用可以看到调用没有定义的height
属性时,是直接返回None
,并没
有报错,这是因为__getattr__()
方法默认是返回None
,我们只需要对不需要的属性调
用,加一个抛出异常的处理就可以了.
...
def __getattr__(self, attr):
if attr == 'name':
return 'Zhazhahui'
if attr == 'sex':
if self.name == 'GuTianle':
return 'male'
return 'female'
raise AttributeError('\'Me\' object has no attribute \'{attr}\''.format(attr=attr)
当然动态返回一个属性可以,返回一个方法当然也是可以的,比如给类加一个返回年龄的 方法:
...
def __getattr__(self, attr):
if attr == 'age':
return lambda: 20
现在,虽然类中没有age
方法,但是调用他并不会保错:
>>> me.age()
20
__iter__和__getitem__
如果一个类中定义了__iter__()
或者__getitem__()
方法,那么这个类就是可迭代类。
如果我们需要写一个迭代器类,即是希望一个类能被for in
循环,就必须要定义一个
__iter__()
方法,有了__iter__()
方法,我们还需要__next__()
方法
(__next__()
不能还有参数)用来循环调用返回下一个值,知道遇到StopIteration
错
误退出循环。我们以一个循环为例:
class Adder(object):
def __init__(self):
self.a = 0
def __iter__(self):
return self
def __next__(self):
self.a += 1
if self.a > 100:
raise StopIteration()
return self.a
循环这个Adder()
实例:
>>> for i in Adder():
... print(i)
...
1
2
3
...
100
虽然__iter__()
方法只是返回自身,似乎只要实现__next__()
方法就可以了,其实不
然,程序在循环迭代的时候会首先查找实例是否有__iter__()
方法,来确定他是否是一
个迭代对象。如果我们去掉__iter__()
方法,再次调用就会得到报错:
TypeError: 'Adder' object is not iterable
如果实例中没有__iter__
但是有__getitem__
的时候,也可以被迭代。循环会从0
开
始依次读取相应的下标,直到发生IndexError
为止:
class Test(object):
def __getitem__(self, n):
a = 0
for i in range(n):
a += 1
return a
循环这个实例:
>>> t = Test()
>>> for i in t:
... print(i)
...
0
1
2
......
__getitem__()
方法最主要的功能是让实例像list一样按住所以取出元素,仍然是上面的
例子:
>>> t = Test()
>>> t[0]
0
>>> t[99]
99
>>> t[1000]
1000
如果要想像 list 那样实现切片功能,就需要判断__getitem__()
方法传入的参数是
int
还是切片对象slice
:
...
def __getitem__(self, n):
a = 0
if isinstance(n, int):
...
if isinstance(n, slice):
start = n.start
stop = n.stop
if start is None:
start = 0
if stop is None:
stop = 100
List = []
for i in range(stop):
if i >= start:
List.append(a)
a += 1
return List
调用实例:
>>> t = Test()
>>> t[:10]
[0,1,2,3,4,5,6,7,8,9]
当然这只是基础的切片处理,还有很多比如对step
参数,负数,做处理。但是这也不难
看出__getitem__()
的强大。对应的是__setitem__()
方法,把对象视作 list 或 dict
来对集合赋值。最后,还有__delitem__()
方法,用于删除某个元素。
__len__
如果一个类的某个属性是一个集合,如果想要使用len()
函数直接获取类的某个属性有多
少个属性,类必须提供一个__len__()
方法,让他定义返回某个属性元素的个数:
class Test(object):
def __init__(self, n):
a, test_list = 0, []
for i in range(n):
if a % 2 == 0:
test_list.append(a)
a += 1
self.L = test_list
def __len__(self):
return len(self.L)
def __repr__(self):
return str(self.L)
调用测试:
>>> t = Test(10)
>>> t
[0, 2, 4, 6, 8]
>>> len(t)
5
有了__len__()
方法我们可以很方便的去定制我们的类,去返回我们需要返回的属性个
数。
结尾
本篇笔记主要总结了常遇到的python
类的定制方法,不难看出这些定制方法可以让我们
很方便的定制一些特殊的类。至于还有一些比如实现加运算的:__add__
,实现减运算
的:__sub__
,乘和除运算的:__mul__
,__div__
。大家可以参考文档自己了解。