python学习(六)-定制类
形如__xxx__
的变量或者函数名在python中有特殊用途
__str__和__repr__
把一个类的实例变成 str,就需要实现特殊方法 __str__()
__str__()
用于显示给用户,而__repr__()
用于显示给开发人员
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object (name=%s)' % self.name
__repr__ = __str__
def __cmp__(self, s):
if self.name < s.name:
return -1
elif self.name > s.name:
return 1
else:
return 0
print(Student('Michael'))
Student object (name: Michael)
上述 Student 类实现了__cmp__()
方法,__cmp__
用实例自身self和传入的实例 s 进行比较
Student类实现了按name进行排序:
>>> L = [Student('Tim', 99), Student('Bob', 88), Student('Alice', 77)]
>>> print sorted(L)
[(Alice: 77), (Bob: 88), (Tim: 99)]
len() 函数工作正常,类必须提供一个特殊方法__len__()
,它返回元素的个数。
class Fib(object):
def __init__(self, num):
a, b, L = 0, 1, []
for n in range(num):
L.append(a)
a, b = b, a+b
self.numbers = L
def __str__(self):
return str(self.numbers)
__repr__ = __str__
def __len__(self):
return len(self.numbers)
f = Fib(10)
print f [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
print len(f) 10
有理数加减乘除运算
加法运算:__add__
减法运算:__sub__
乘法运算:__mul__
除法运算:__div__
让int()函数正常工作,只需要实现特殊方法__int__()
让float()函数正常工作,只需要实现特殊方法__float__()
class Rational(object):
def __init__(self, p, q):
self.p = p
self.q = q
def __int__(self):
return self.p // self.q
def __float__(self):
return float(self.p) / self.q
print float(Rational(7, 2))
print float(Rational(1, 3))
__slots__
限制添加属性
class Person(object):
__slots__ = ('name', 'gender')
def __init__(self, name, gender):
self.name = name
self.gender = gender
只允许添加name和gender
一个类实例也可以变成一个可调用对象,只需要实现一个特殊方法__call__()
。
class Fib(object):
def __call__(self,num):
a,b,L =0,1,[]
for x in range(num):
L.append(a)
a,b = b,a+b
return L
f = Fib()
print f(10) [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
判断一个对象是否能被调用,能被调用的对象就是一个Callable对象
>>> callable(Student())
True
>>> callable(max)
True
只有在没有找到属性的情况下,才调用__getattr__
利用完全动态的__getattr__,我们可以写出一个链式调用:
class Chain(object):
def __init__(self, path=''):
self._path = path
def __getattr__(self, path):
return Chain('%s/%s' % (self._path, path))
def __str__(self):
return self._path
__repr__ = __str__
试试:
>>> Chain().status.user.timeline.list
'/status/user/timeline/list'
__iter__
如果一个类想被用于for ... in循环,类似list或tuple那样,就必须实现一个iter()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的next()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。
我们以斐波那契数列为例,写一个Fib类,可以作用于for循环:
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 # 返回下一个值
现在,试试把Fib实例作用于for循环:
>>> for n in Fib():
... print(n)
...
1
1
2
3
5
...
46368
75025
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
__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]
Python内置的@property装饰器就是负责把一个方法变成属性调用
class Student(object):
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
@property的实现比较复杂,我们先考察如何使用。把一个getter方法变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作:
>>> s = Student()
>>> s.score = 60 # OK,实际转化为s.set_score(60)
>>> s.score # OK,实际转化为s.get_score()
60
>>> s.score = 9999
Traceback (most recent call last):
...
ValueError: score must between 0 ~ 100!