Python3之定制类
看到类似的__slots__这种形如__xxx__的变量或者函数名就要注意,这些在Python中是有特殊用途的
Python中还有许多有特殊用途的函数,可以帮助我们定制类
__str__
先定义一个Student类,打印一个实例
1 2 3 4 5 6 | >>> class Student( object ): ... def __init__( self ,name): ... self .name = name ... >>> print (Student( 'Zhangsan' )) <__main__.Student object at 0x7f8a4830a748 > |
打印出<__main__.Student object at 0x7f8a4830a748>不好看,不直观
怎么才能打印的好看呢?只需要定义好__str__()方法,返回一个好看的字符串就可以了
1 2 3 4 5 6 7 8 9 | >>> class Student( object ): ... def __init__( self ,name): ... self .name = name ... def __str__( self ): ... return 'Student object(name:%s)' % self .name ... >>> >>> print (Student( 'Zhangsan' )) Student object (name:Zhangsan) |
这样打印出来的实例,不但好看 ,而且容易看出实例内部重要的数据
但是如果直接在终端敲变量而不用print,打印出来还是一样不好看
1 2 | >>> Student( 'Zhangsan' ) <__main__.Student object at 0x7f8a4830a8d0 > |
这是因为直接显示变量调用的不是__str__()
,而是__repr__()
,两者的区别是__str__()
返回用户看到的字符串,而__repr__()
返回程序开发者看到的字符串,也就是说,__repr__()
是为调试服务的。
解决办法是再定义一个__repr__()
。但是通常__str__()
和__repr__()
代码都是一样的,所以,有个偷懒的写法:
1 2 3 4 5 6 7 8 9 10 | >>> class Student( object ): ... def __init__( self ,name): ... self .name = name ... def __str__( self ): ... return 'Student object(name:%s)' % self .name ... __repr__ = __str__ ... >>> >>> Student( 'Zhangsan' ) Student object (name:Zhangsan) |
__iter__
如果一个类想被用于for..in循环,类似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__()方法循环的拿到下一个值,直到遇到StopIteration错误退出循环
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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> 10000 : raise StopIteration() return self .a for n in Fib(): print (n) |
输出
1 2 3 4 5 6 7 8 9 10 11 | >>> for n in Fib(): ... print (n) ... 1 1 2 3 5 ... 46368 75025 |
__getitem__
Fib实例虽然能作用于for循环,看起来和list有点像,但是,把它当成list来使用还是不行,比如,取第5个元素
1 2 3 4 | >>> Fib()[ 5 ] Traceback (most recent call last): File "<stdin>" , line 1 , in <module> TypeError: 'Fib' object is not subscriptable |
要表现的象list那样按照下标取出元素,需要实现__getitem__()方法
special_getitem.py
1 2 3 4 5 6 | class Fib( object ): def __getitem__( self ,n): a,b = 1 , 1 for x in range (n): a,b = b,a + b return a |
现在,就可以按下标访问数列的任意一项了
1 2 3 4 5 6 | >>> f[ 0 ] 1 >>> f[ 1 ] 1 >>> f[ 100 ] 573147844013817084101 |
f[0]相当于把n=0参数传递给方法__getitem__(0),因为n=0,for循环不执行,返回a=1,
f[1]相当于把n=1参数传递给方法__getitem__(1),因为n=1,for循环执行一次a=b=1,b=a+b=2 返回值为a=1
f[2]相当于把n=2参数传递给方法__geritem__(2),第一次循环,a=b=1,b=a+b=2 再次循环 a=b=2,b=a+b=1+2=3 两次循环结束返回值a=2
以此类推
但是list有个切片的方法对应Fib确报错
1 2 3 4 5 | >>> f[ 1 : 5 ] Traceback (most recent call last): File "<stdin>" , line 1 , in <module> File "<stdin>" , line 4 , in __getitem__ TypeError: 'slice' object cannot be interpreted as an integer |
原因是__getitem__()传入的参数可能是一个int,也可以是一个切片对象slice,所以要判断
special_getitem.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 | class Fib( object ): def __getitem__( self ,n): #n是整数 if isinstance (n, int ): a,b = 1 , 1 for x in range (n): a,b = b,a + b return a #n是切片类似[0:2] if isinstance (n, slice ): 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 f = Fib() print (f[ 0 ]) print (f[ 1 ]) print (f[ 2 ]) print (f[ 3 ]) print (f[ 0 : 3 ]) |
如果传递的参数是整数则和上面的方法不变,一次返回一个整数
如果传递的参数是切片slice则从切片的start开始至stop结束返回一个列表
运行结果如下
1 2 3 4 5 | 1 1 2 3 [ 1 , 1 , 2 ] |
如果传递参数是切片,执行过程分析f[0:1]切片取索引为0及第一个元素的列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | if 判断[ 0 : 1 ]是 slice start = n.start 所以start = None stop = n.stop stop = 1 if 判断如果start为 None 则把start置为 0 定义初始两位数为 1 , 1 定义空列表L = [] 执行循环 for x in range ( 1 ): x = 1 执行第一次循环 if 判断 1 > = 1 满足条件 执行append语句后L = [ 1 ] 接着执行往下语句a,b = b,a + b a = b,b = a + b是同时执行执行完毕后 a = 1 b = 2 退出 for 循环返回列表L = [ 1 ] |
如果传递的切片参数为[0:2]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | if 判断[ 0 : 2 ]是 slice start = n.start 所以start = None stop = n.stop stop = 2 if 判断如果start为 None 则把start置为 0 定义初始两位数为 1 , 1 定义空列表L = [] 执行循环 for x in range ( 1 ): x = 0 执行第一次循环 if 判断 0 > = 0 满足条件 执行append语句后L.append(a) 及L.append( 1 ) L = [ 1 ] 接着执行往下语句a,b = b,a + b a = b,b = a + b是同时执行执行完毕后 a = 1 b = 2 for 循环返回列表L = [ 1 ] x = 1 执行第二次循环 if 判断 1 > = 1 满足条件 执行append语句L.append(a)及L.append( 1 ) L = [ 1 , 1 ]<br>执行a,b = b,a + b执行完毕后<br>a = 2 b = 3 因为stop = 2 执行两次以后退出循环 返回列表L = [ 1 , 1 ] |
如果传递是切片参数是[0:3]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | if 判断[ 0 : 3 ]是 slice start = n.start 所以start = None stop = n.stop stop = 3 if 判断如果start为 None 则把start置为 0 定义初始两位数为 1 , 1 定义空列表L = [] 执行循环 for x in range ( 1 ): x = 0 执行第一次循环 if 判断 0 > = 0 满足条件 执行append语句后L.append(a) 及L.append( 1 ) L = [ 1 ] 接着执行往下语句a,b = b,a + b a = b,b = a + b是同时执行执行完毕后 a = 1 b = 2 for 循环返回列表L = [ 1 ] x = 1 执行第二次循环 if 判断 1 > = 0 满足条件 执行append语句L.append(a)及L.append( 1 ) L = [ 1 , 1 ] 执行a,b = b,a + b执行完毕后 a = 2 b = 3 x = 2 执行第三次循环 if 判断 2 > = 0 满足条件 执行append语句L.append(a)及L.append( 2 ) L = [ 1 , 1 , 2 ] 因为stop = 3 执行三次次以后退出循环 返回列表L = [ 1 , 1 , 2 ] |
以此类推
如果start不是 0
1 2 | for 循环会继续执行,但是还没有到start处因为不满足x> = start条件所以L.append(a)不会执行,不会往列表内追加元素 但是a,b = b,a + b会继续执行 |
关于切片及切片函数slice参考:https://www.cnblogs.com/minseo/p/11113638.html
但是以上还是有缺陷没有对step参数做处理
1 2 | print (f[ 0 : 10 : 2 ]) [ 1 , 1 , 2 , 3 , 5 , 8 , 13 , 21 , 34 , 55 ] |
虽然加了参数:2步数还是1
也没有对负数进行处理,所以,要正确实现以下__getitem__()还有很多工作要做
此外,如果把对象看成dict,__getitem__()的参数也可能是一个可以做key的object,例如str。
与之对应的是__setitem__()
方法,把对象视作list或dict来对集合赋值。最后,还有一个__delitem__()
方法,用于删除某个元素。
__getattr__
正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错。比如定了Student类
1 2 3 | class Student( object ): def __init__( self ): self .name = "Zhangsan" |
调用存在的属性name没有问题,但是调用不存在的score属性就有问题了
1 2 3 4 5 6 | >>> s.name 'Zhangsan' >>> s.score Traceback (most recent call last): File "<stdin>" , line 1 , in <module> AttributeError: 'Student' object has no attribute 'score' |
错误信息很清楚地告诉我们,没有找到score这个attribute
要避免这个错误,除了可以加上一个score属性外,python还有另一个机制,那就是写一个__getattr__()方法,动态返回一个属性
special_getattr.py
1 2 3 4 5 6 | class Student( object ): def __init__( self ): self .name = 'Michael' def __getattr__( self , attr): if attr = = 'score' : return 99 |
当调用不存在的属性时,比如score
,Python解释器会试图调用__getattr__(self, 'score')
来尝试获得属性,这样,我们就有机会返回score
的值:
1 2 3 4 5 | >>> s = Student() >>> s.name 'Michael' >>> s.score 99 |
返回函数也是完全可以的
1 2 3 4 5 6 7 8 9 10 11 12 | class Student( object ): def __init__( self ): self .name = 'Michael' def __getattr__( self , attr): if attr = = 'score' : return 99 def __getattr__( self ,attr): if attr = = 'age' : return lambda : 25 s = Student() print (s.age()) |
调用方式改为s.age
注意,只有在没有找到属性的情况下,才调用__getattr__,已有的属性比如name,不会再__getattr__中查找
此外,注意到任意调用如s.abc
都会返回None
,这是因为我们定义的__getattr__
默认返回就是None
。要让class只响应特定的几个属性,我们就要按照约定,抛出AttributeError
的错误:
1 2 3 4 5 6 | >>> s = Student() >>> s.name 'Michael' >>> s.aba >>> print (s.aba) None |
改成如下
1 2 3 4 | def __getattr__( self ,attr): if attr = = 'age' : return lambda : 25 raise AttributeError( '\'Student\' object has no attribute \'%s\'' % attr) |
__call__一个对象实例可以有自己是属性和方法,当我们调用实例方法时,我们用instance.method()来调用,能不能直接在实例本身上调用呢?
任何类,只需要定义一个__call__()方法,就可以直接对实例进行调用
special_call.py
1 2 3 4 5 | class Student( object ): def __init__( self ,name): self .name = name def __call__( self ): print ( 'My name is %s.' % self .name) |
调用方法如下
1 2 3 | >>> s = Student( 'Zhansan' ) >>> s() My name is Zhansan. |
__call__()
还可以定义参数。对实例进行直接调用就好比对一个函数进行调用一样,所以你完全可以把对象看成函数,把函数看成对象,因为这两者之间本来就没啥根本的区别。
如果你把对象看成函数,那么函数本身其实也可以在运行期动态创建出来,因为类的实例都是运行期创建出来的,这么一来,我们就模糊了对象和函数的界限。
那么,怎么判断一个变量是对象还是函数呢?其实,更多的时候,我们需要判断一个对象是否能被调用,能被调用的对象就是一个Callable
对象,比如函数和我们上面定义的带有__call__()
的类实例:
1 2 3 4 5 6 7 8 9 10 | >>> callable (Student( 'Zhangsan' )) True >>> callable ( max ) True >>> callable ([ 1 , 2 , 3 ]) False >>> callable ( None ) False >>> callable ( 'str' ) False |
通过callable()
函数,我们就可以判断一个对象是否是“可调用”对象。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
2018-06-29 Mongodb之主从复制
2017-06-29 ELKStack生产案例