Python面向对象-定制方法

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 "", line 1, in 
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 "", line 1, in 
  File "", 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])

更多的内置函数对象参见官网

 

你可能感兴趣的:(Python面向对象-定制方法)