前言
打算写一系列文章来记录自己学习 Python 3 的点滴;本章主要介绍 Python 有关面向对象编程中的通过重载内置方法自定义类的类型和外观;
本文为作者的原创作品,转载需注明出处;
重载内置方法
首先,内置方法在 Python 中约定使用 __<name>__
的形式进行定义的;通过重载 Python 的内置方法,可以自定义你的类,
- 使得它的默认行为发生改变;
- 并且使得它的类的
类型
发生变化
;
object 对象
Python 为类定义了大量的内置方法,而这些内置方法都是通过继承对象 object 得到的,看看 object 的源码,
builtins.py
1 | class object: |
可见,里面定义了大量的内置方法,下面,笔者就几个常用的内置函数进行梳理;
__str__
这个等价于 Java 对象的 String toString() 方法,当该对象将要被打印出来的时候,会被重定向到该方法并输出其调用结果;
1 | class Student(object): |
可见,打印出来的东西实际上于我们而言帮助不大;是否能够重新定义其输出内容使其输出更有价值的内容呢?
1 | class Student(object): |
通过覆盖 Student 的默认内置方法 __str__
,便重写了 Student 实例的默认打印输出格式;
1 | 'Linda')) print(Student( |
__repr__
通过__str__的方式,我们重写了使用 print 方法打印实例的输出内容,但是,如果,我们直接使用下面的这种方式,可以发现,输出的还是以前的方式
1 | 'Linda')) linda = Student( |
这个时候,我们可以通过重写 __repr__
来重定义其输出内容;
1 | class Student(object): |
再执行
1 | 'Linda')) linda = Student( |
__iter__
__iter__
比较有意思,其作用相当于 Java 对象实现了接口 Iterator 一样,使得当前类可以被迭代操作,当然 Python 中最典型的迭代操作就是for ... in ...
的方式了;首先要特别注意的一点是,__iter__
方法必须返回的是一个Iterator
对象,有关 Iterator 的介绍参考 Python 系列学习六:迭代类型 Iterator / Iterable,下面做一个 Negative 的测试,返回一个 list(list 是一个 Iterable 对象,不是 Iterator);
1 | class Numbers(object): |
执行,
1 | n = Numbers() |
可见,在执行内置方法 iter() 的时候抛出了一个类型异常的错误,该错误很明显的指出了,类型不匹配,返回的对象 list 不是一个 Iterator
;所以,需要做如下的修改该,将其改为 Iterator 对象,如何将 list 转变为 Iterator 对象参考 Iterable -> Iterator
1 | class Numbers(object): |
通过内置方法iter(Iterable)
将 list 转换为了Iterator
,再试试;
1 | n = Numbers() |
类型的变化
别急,很多人学到这里,就匆匆忙忙的走了,却忘了街边还有一幅最美丽的风景,那就是实例 n 的类型现在是什么?
1 | type(n) |
切,楼主,还以为你要说什么呢?我知道呀,它本来就是一个 Numbers 对象嘛…. 等等,别着急,看看下面这个,
1 | from collections import Iterable |
有意思吧,当一个类的定义当中,如果重载了内置方法__iter__(self)
,他将会成为Iterable
类型的对象了;
为了确保以上的论述是正确的,我们再来看看,
1 | class Numbers(object): |
ok,当 Numbers 类没有重载内置方法__iter__
的时候,它并不是一个Iterable
对象;
1 | class Numbers(object): |
ok,一旦 Numbers 重载了内置方法__iter__
,它的实例就是一个Iterable
对象了;
有意思吧,这里势必要记住这样的特性;
结合 __next__
结合__next__
方法,使得该对象本身成为一个Iterator
对象,满足__iter__
接口方法的需要;
1 | class Numbers(object): |
上述实现中,通过 __iter__
返回对象实例自己 self,表示,该实例自己就是一个 Iterator 类型的实例,那为什么说该实例自己就是一个 Iterator 实例呢?答案就在,Numbers 类实现了 __next__
方法,使其成为了Iterator
实例,也就满足了接口__iter__
对返回对象是Iterator
类型的需要;执行下看看,
1 | n = Numbers() |
再来看看 Numbers 的实例 n 的类型,
1 | from collections import Iterator |
果然,只要实现了默认内置接口__next__
方法,该实例的类型就自动的转变为了Iterator
;
__getitem__
这里,我们依然使用到 __iter__ 中所使用到的例子,为了后续的演示,将它做了适当的改动,将 list 赋值给了实例变量l
;
1 | class Numbers(object): |
虽然,Numbers 的实例可以进行迭代
1 | n = Numbers() |
但是,它不支持 indexing 和切片的操作,比如
1 | 3] n[ |
1 | 1,5] n[ |
要想实现 indexing 和切片的操作,那么就必须实现内置接口__getitem__
,
实现 indexing
那么,笔者首先带领大家通过重载接口方法__getitem__
来实现 indexing 操作,
1 | class Numbers(object): |
执行测试,
1 | n = Numbers() |
可见,通过重载接口方法__getitem__
我们非常容易的实现了 indexing 操作;那切片操作呢?
实现切片
因为切片对应的是另一种参数类型 slice,所以,为了同时兼容 indexing 和切片,就需要在__getitem__
方法中进行判断两种不同的类型并且分别实现对应的逻辑,如下所述,
1 | class Numbers(object): |
执行试试,
1 | n = Numbers() |
这样,我们就兼容了 indexing 和切片的操作;
__getattr__
当在获取默认属性时,没有找到的前提下,会调用__getattr__
检索;例如,我们有如下的测试用例,
1 | class Student(object): |
当我们获取 linda 的成员变量 name 的时候,自然是报错的,提示属性 name 不存在;
1 | linda = Student() |
这个时候,我们就可以利用__getattr__
来处理这种情况了,进一步修改 Student
1 | class Student(object): |
再次调用,
1 | linda = Student() |
可见,当实现了默认接口__getattr__
以后,当实例中没有对应的属性的时候,会交给__getattr__
方法进行处理;不过返回的是默认值;
也可以返回不存在的方法调用的默认值,
1 | class Student(object): |
试试,
1 | linda = Student() |
注意,当实现了默认接口__getattr__
以后,如果某个属性找不到,直接返回None
,并不会有任何的错误提示信息;
1 | linda.score |
可见,再次访问一个不存在的属性 score,并且__getattr__
也没有进行处理的情况下,直接返回None
;所以,我们需要定制错误提示的信息;
1 | class Student(object): |
再次调用
1 | linda.score |
__call__
有时候,某些实例有默认的调用方法,那么为了简化,能不能直接以instance()
的方法进行调用呢?答案同样是有的,只要是利于简化的东西,在 Python 中都是被考虑的,这个简化动作就是由__call__
内置接口函数负责实现的;看下面这个例子,
1 | class Student(object): |
执行测试,
1 | 'Linda') linda = Student( |
可见,实例对象本身,化身成为了一个可被调用的函数,可以直接通过linda()
来进行调用,该调用执行的正是__call__
方法;
判断一个对象是不是一个直接可被调用的对象,可以使用如下两种判断的方式
使用内置方法
callable(instance)
1
2
3
4
5
6
7
8callable(linda)
True
callable(max)
True
1, 2, 3]) callable([
False
None) callable(
False使用
isinstance
1
2
3
4
5
6
7
8
9from collections import Callable
isinstance(linda, Callable)
True
isinstance(max, Callable)
True
1, 2, 3], Callable) isinstance([
False
None, Callable) isinstance(
False