Python 系列学习十三:面向对象编程 - 重载内置方法
前言
打算写一系列文章来记录自己学习 Python 3 的点滴;本章主要介绍 Python 有关面向对象编程中的通过重载内置方法自定义类的类型和外观;
本文为作者的原创作品,转载需注明出处;
重载内置方法
首先,内置方法在 Python 中约定使用 __<name>__ 的形式进行定义的;通过重载 Python 的内置方法,可以自定义你的类,
- 使得它的默认行为发生改变;
- 并且使得它的类的
类型发生变化;
object 对象
Python 为类定义了大量的内置方法,而这些内置方法都是通过继承对象 object 得到的,看看 object 的源码,
builtins.py
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
|
class object:
""" The most base type """
def __delattr__(self, *args, **kwargs): # real signature unknown
""" Implement delattr(self, name). """
pass
def __dir__(self): # real signature unknown; restored from __doc__
"""
__dir__() -> list
default dir() implementation
"""
return []
def __eq__(self, *args, **kwargs): # real signature unknown
""" Return self==value. """
pass
def __format__(self, *args, **kwargs): # real signature unknown
""" default object formatter """
pass
def __getattribute__(self, *args, **kwargs): # real signature unknown
""" Return getattr(self, name). """
pass
def __ge__(self, *args, **kwargs): # real signature unknown
""" Return self>=value. """
pass
def __gt__(self, *args, **kwargs): # real signature unknown
""" Return self>value. """
pass
def __hash__(self, *args, **kwargs): # real signature unknown
""" Return hash(self). """
pass
def __init_subclass__(self, *args, **kwargs): # real signature unknown
"""
This method is called when a class is subclassed.
The default implementation does nothing. It may be
overridden to extend subclasses.
"""
pass
def __init__(self): # known special case of object.__init__
""" Initialize self. See help(type(self)) for accurate signature. """
pass
def __le__(self, *args, **kwargs): # real signature unknown
""" Return self<=value. """
pass
def __lt__(self, *args, **kwargs): # real signature unknown
""" Return self<value. """
pass
def __new__(cls, *more): # known special case of object.__new__
""" Create and return a new object. See help(type) for accurate signature. """
pass
def __ne__(self, *args, **kwargs): # real signature unknown
""" Return self!=value. """
pass
def __reduce_ex__(self, *args, **kwargs): # real signature unknown
""" helper for pickle """
pass
def __reduce__(self, *args, **kwargs): # real signature unknown
""" helper for pickle """
pass
def __repr__(self, *args, **kwargs): # real signature unknown
""" Return repr(self). """
pass
def __setattr__(self, *args, **kwargs): # real signature unknown
""" Implement setattr(self, name, value). """
pass
def __sizeof__(self): # real signature unknown; restored from __doc__
"""
__sizeof__() -> int
size of object in memory, in bytes
"""
return 0
def __str__(self, *args, **kwargs): # real signature unknown
""" Return str(self). """
pass
def __subclasshook__(cls, subclass): # known special case of object.__subclasshook__
"""
Abstract classes can override this to customize issubclass().
This is invoked early on by abc.ABCMeta.__subclasscheck__().
It should return True, False or NotImplemented. If it returns
NotImplemented, the normal algorithm is used. Otherwise, it
overrides the normal algorithm (and the outcome is cached).
"""
pass
__class__ = None # (!) forward: type, real value is ''
__dict__ = {}
__doc__ = ''
__module__ = ''
|
可见,里面定义了大量的内置方法,下面,笔者就几个常用的内置函数进行梳理;
__str__
这个等价于 Java 对象的 String toString() 方法,当该对象将要被打印出来的时候,会被重定向到该方法并输出其调用结果;
|
1
2
3
4
5
6
|
...
<__main__.Student object at 0x589cdf190>
|
可见,打印出来的东西实际上于我们而言帮助不大;是否能够重新定义其输出内容使其输出更有价值的内容呢?
|
1
2
3
4
5
6
|
...
|
通过覆盖 Student 的默认内置方法 __str__,便重写了 Student 实例的默认打印输出格式;
|
1
2
|
Student object (name: Linda)
|
__repr__
通过__str__的方式,我们重写了使用 print 方法打印实例的输出内容,但是,如果,我们直接使用下面的这种方式,可以发现,输出的还是以前的方式
|
1
2
3
|
<__main__.Student object at 0x589cdf190>
|
这个时候,我们可以通过重写 __repr__ 来重定义其输出内容;
|
1
2
3
4
5
6
|
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object (name=%s)' % self.name
__repr__ = __str__
|
再执行
|
1
2
3
|
Student object (name: Linda)
|
__iter__
__iter__比较有意思,其作用相当于 Java 对象实现了接口 Iterator 一样,使得当前类可以被迭代操作,当然 Python 中最典型的迭代操作就是for ... in ...的方式了;首先要特别注意的一点是,__iter__方法必须返回的是一个Iterator对象,有关 Iterator 的介绍参考Python 系列学习六:迭代类型 Iterator / Iterable,下面做一个 Negative 的测试,返回一个 list(list 是一个 Iterable 对象,不是 Iterator);
|
1
2
3
|
class Numbers(object):
def __iter__(self):
return list(range(1,11))
|
执行,
|
1
2
3
4
5
6
7
|
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: iter() returned non-iterator of type 'list'
|
可见,在执行内置方法 iter() 的时候抛出了一个类型异常的错误,该错误很明显的指出了,类型不匹配,返回的对象 list 不是一个Iterator;所以,需要做如下的修改该,将其改为 Iterator 对象,如何将 list 转变为 Iterator 对象参考 Iterable -> Iterator
|
1
2
3
|
class Numbers(object):
def __iter__(self):
return iter(list(range(1,11)))
|
通过内置方法iter(Iterable)将 list 转换为了Iterator,再试试;
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
1
2
3
4
5
6
7
8
9
10
|
类型的变化
别急,很多人学到这里,就匆匆忙忙的走了,却忘了街边还有一幅最美丽的风景,那就是实例 n 的类型现在是什么?
|
1
2
|
<class '__main__.Numbers'>
|
切,楼主,还以为你要说什么呢?我知道呀,它本来就是一个 Numbers 对象嘛…. 等等,别着急,看看下面这个,
|
1
2
3
|
True
|
有意思吧,当一个类的定义当中,如果重载了内置方法__iter__(self),他将会成为Iterable类型的对象了;
为了确保以上的论述是正确的,我们再来看看,
|
1
2
3
4
5
6
|
False
|
ok,当 Numbers 类没有重载内置方法__iter__的时候,它并不是一个Iterable对象;
|
1
2
3
4
5
6
7
|
True
|
ok,一旦 Numbers 重载了内置方法__iter__,它的实例就是一个Iterable对象了;
有意思吧,这里势必要记住这样的特性;
结合 __next__
结合__next__方法,使得该对象本身成为一个Iterator对象,满足__iter__接口方法的需要;
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
class Numbers(object):
def __init__(self):
self.nums = list(range(1, 11))
self.index = 0
def __iter__(self):
return self
def __next__(self):
if( self.index <= 9 ):
r = self.nums[self.index]
self.index += 1
return r
else:
raise StopIteration()
return r
|
上述实现中,通过 __iter__ 返回对象实例自己 self,表示,该实例自己就是一个 Iterator 类型的实例,那为什么说该实例自己就是一个Iterator 实例呢?答案就在,Numbers 类实现了 __next__方法,使其成为了Iterator实例,也就满足了接口__iter__对返回对象是Iterator类型的需要;执行下看看,
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
1
2
3
4
5
6
7
8
9
10
|
再来看看 Numbers 的实例 n 的类型,
|
1
2
3
|
True
|
果然,只要实现了默认内置接口__next__方法,该实例的类型就自动的转变为了Iterator;
__getitem__
这里,我们依然使用到 __iter__ 中所使用到的例子,为了后续的演示,将它做了适当的改动,将 list 赋值给了实例变量l;
|
1
2
3
4
5
|
class Numbers(object):
def __init__(self):
self.l = list(range(1,11))
def __iter__(self):
return iter(self.l)
|
虽然,Numbers 的实例可以进行迭代
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
1
2
3
4
5
6
7
8
9
10
|
但是,它不支持 indexing 和切片的操作,比如
|
1
2
3
4
|
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'Numbers' object does not support indexing
|
|
1
2
3
4
|
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'Numbers' object is not subscriptable
|
要想实现 indexing 和切片的操作,那么就必须实现内置接口__getitem__,
实现 indexing
那么,笔者首先带领大家通过重载接口方法__getitem__来实现 indexing 操作,
|
1
2
3
4
5
6
7
|
class Numbers(object):
def __init__(self):
self.l = list(range(1,11))
def __iter__(self):
return iter(self.l)
def __getitem__(self, n):
return self.l[n]
|
执行测试,
|
1
2
3
|
6
|
可见,通过重载接口方法__getitem__我们非常容易的实现了 indexing 操作;那切片操作呢?
实现切片
因为切片对应的是另一种参数类型 slice,所以,为了同时兼容 indexing 和切片,就需要在__getitem__方法中进行判断两种不同的类型并且分别实现对应的逻辑,如下所述,
|
1
2
3
4
5
6
7
8
9
10
|
class Numbers(object):
def __init__(self):
self.l = list(range(1,11))
def __iter__(self):
return iter(self.l)
def __getitem__(self, n):
if isinstance(n, int):
return self.l[n]
if isinstance(n, slice):
return self.l[n.start:n.stop]
|
执行试试,
|
1
2
3
4
5
|
6
[6, 7, 8, 9, 10]
|
这样,我们就兼容了 indexing 和切片的操作;
__getattr__
当在获取默认属性时,没有找到的前提下,会调用__getattr__检索;例如,我们有如下的测试用例,
|
1
2
|
class Student(object):
pass
|
当我们获取 linda 的成员变量 name 的时候,自然是报错的,提示属性 name 不存在;
|
1
2
3
4
5
|
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'name'
|
这个时候,我们就可以利用__getattr__来处理这种情况了,进一步修改 Student
|
1
2
3
4
|
class Student(object):
def __getattr__(self, attr):
if attr == 'name':
return 'default person'
|
再次调用,
|
1
2
3
|
'default person'
|
可见,当实现了默认接口__getattr__以后,当实例中没有对应的属性的时候,会交给__getattr__方法进行处理;不过返回的是默认值;
也可以返回不存在的方法调用的默认值,
|
1
2
3
4
5
6
|
class Student(object):
def __getattr__(self, attr):
if attr == 'name':
return 'default person'
if attr == 'age':
return lambda: 16
|
试试,
|
1
2
3
|
16
|
注意,当实现了默认接口__getattr__以后,如果某个属性找不到,直接返回None,并不会有任何的错误提示信息;
|
1
2
|
>>>
|
可见,再次访问一个不存在的属性 score,并且__getattr__也没有进行处理的情况下,直接返回None;所以,我们需要定制错误提示的信息;
|
1
2
3
4
5
6
7
|
class Student(object):
def __getattr__(self, attr):
if attr == 'name':
return 'default person'
if attr == 'age':
return lambda: 16
raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)
|
再次调用
|
1
2
3
4
5
|
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in __getattr__
AttributeError: 'Student' object has no attribute 'score'
|
__call__
有时候,某些实例有默认的调用方法,那么为了简化,能不能直接以instance()的方法进行调用呢?答案同样是有的,只要是利于简化的东西,在 Python 中都是被考虑的,这个简化动作就是由__call__内置接口函数负责实现的;看下面这个例子,
|
1
2
3
4
5
|
class Student(object):
def __init__(self, name):
self.name = name
def __call__(self):
print('Student name is %s.' % self.name)
|
执行测试,
|
1
2
3
|
Student name is Linda.
|
可见,实例对象本身,化身成为了一个可被调用的函数,可以直接通过linda()来进行调用,该调用执行的正是__call__方法;
判断一个对象是不是一个直接可被调用的对象,可以使用如下两种判断的方式
-
使用内置方法
callable(instance)12345678TrueTrueFalseFalse -
使用
isinstance123456789TrueTrueFalseFalse

浙公网安备 33010602011771号