Python中的class可以定义许多定制方法,可以让我们方便的生成特定的类。
我们之前介绍了__slots__、__len__(),python中还有许多这样的特殊函数:
__str__
>>> class Student(object): ... def __init__(self,name): ... self.name = name ... >>> print(Student('wc')) <__main__.Student object at 0x035F3630>
上面我们定义了一个普通的Student类,打印出的是一堆<__main__.Student object at 0x035F3630> 。如何自定义打印的内容呢?比如我们关心的"WC"呢。这时__str__()派上用场了:
>>> class Student(object): ... def __init__(self,name): ... self.name = name ... def __str__(self): ... return '%s'%self.name ... >>> print(Student('wc')) wc
在看看我们直接调用变量会是什么情况:
>>> s = Student('wc') >>> s <__main__.Student object at 0x035F3AB0>
当我们直接调用变量的时候,怎么自定义打印的内容呢?使用__repr__()函数:
>>> class Student(object): ... def __init__(self,name): ... self.name = name ... def __str__(self): ... return '%s'%self.name ... def __repr__(self): ... return '%s'%self.name ... >>> print(Student('wc')) wc >>> s = Student('wc') >>> s wc
一般来说,__str__和__repr__定义的内容是一样的,只是前者是给用户看的,后者给开发者看的。
__iter__
之前我们介绍过可迭代对象:如果我们想让一个类可以被for……in循环,就必须实现方法__iter__()方法。该方法返回一个迭代对象。下面写一个模仿输出斐波拉契数列的Fib类:
>>> 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 > 1000: ... raise StopIteration() ... return self.a ... >>> for i in Fib(): ... print(i) ... 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
__getitem__
尝试把Fib当做list使用,比如取第三个元素:
>>> Fib()[3] Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'Fib' object does not support indexing
不行!
要表现的像list那样按照按照角标去除元素,需要实现__getiten__():
>>> class Fib(object): ... def __getitem__(self,n): ... a,b = 1,1 ... for i in range(n): ... a,b = b,a+b ... return a ... >>> f = Fib() >>> f[3] 3 >>> f[10] 89
再来试试Fib如何使用切片方法:
>>> class Fib(object): ... def __getitem__(self,n): ... if isinstance(n,int): ... a,b = 1,1 ... for i in range(n): ... a,b = b,a+b ... return a ... if isinstance(n,slice): ... start = n.start ... stop = n.stop ... if start is None: ... start = 0 ... a,b = 1,1 ... L = [] ... for i in range(stop): ... if i >= start: ... L.append(a) ... a,b = b,a+b ... return L ... >>> f = Fib() >>> f[0:3] [1, 1, 2] >>> f[:5] [1, 1, 2, 3, 5]
>>> f[:6:2]
[1, 1, 2, 3, 5, 8]
但是,我们还没对负数做处理。要实现一个__getitem__()还有、还可以做很多事情。比如,如果把类看做是一个dict,可以使__getitem__()的参数为key的object。与之对应的是__setitem__()方法,可以把对象视作list或者dict来赋值;此外,还存在__delitem__()方法,用来删除某个元素。
通过上面的方法,我们可以自定义一个类,使其表现的像python自带的list、tuple、dict一样。由此,我们对于‘鸭子类型’的设计有了更深刻的理解。
__getattr__
当我们定义好一个类后,如果想调用类不存在的属性或者方法,我们可以给实例增加一个属性或者方法;其实,python还有另外一个机制,那就是写一个__getattr__()函数,动态返回一个属性。:
>>> class Student(object): ... def __init__(self): ... self.name = 'wc' ... def __getattr__(self,attr): ... if attr == 'score': ... return 88 ... >>> s = Student() >>> s.name 'wc' >>> s.score 88
当调用不存在的属性时,python的机制会调用__getattr__()方法来尝试的得到属性。同样的,也适用于返回函数:
>>> class Student(object): ... def __getattr__(self,attr): ... if attr == 'age': ... return lambda:20 ... >>> s = Student() >>> s.age() 20
注意:只有在没有找到属性的情况下,才会调用__getdattr__()!
继续优化一下:当我们任意调用不存在的属性时,返回指定的错误:
>>> class Student(object): ... def __getattr__(self,attr): ... if attr == 'age': ... return lambda:20 ... raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr) ... >>> s = Student() >>> s.haha Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 5, in __getattr__ AttributeError: 'Student' object has no attribute 'haha'
这实际上把一个类的所有属性和方法调用全部动态化处理了。相当有用。
__call__
一个对象实例可以有自己的属性和方法。我们能不能直接在实例本身上调用呢?既然这么问,那答案是肯定的。任何类只要定义一个__call__()方法,就可以直接对实例进行调用:
>>> class Student(object): ... def __init__(self,name): ... self.name = name ... def __call__(self): ... print('你好:%s'% self.name) ... >>> s = Student('wc') >>> s() 你好:wc
对实例对象进行直接调用就好比对一个函数调用一样,既可以把对象看成函数,把函数看成对象。
如果你把对象看成函数,那么函数本身也可以在运行期间动态创建出来,因为类的实例都是在运行期间创建出来的。
那么怎么判断一个变量是对象还是函数呢?其实更多的时候,我们需要判断一个对象是否能被调用,能被调用的对象就是一个Callable对象:
>>> callable(Student('wc')) True >>> callable(max) True >>> callable(abs) True >>> callable(s) True >>> callable(None) False >>> callable('str') False >>> callable([1,2,3])
更多的内置函数对象参见官网