前言
大家在写 Python 代码的时候有没有这样的疑问。
为什么数学中的 + 号,在字符串运算中却变成拼接功能,如 'ab' + 'cd' 结果为 abcd ;而 * 号变成了重复功能,如 'ab' * 2 结果为 abab 。
为什么某些对象 print 能输出数据,而 print 自定义的类对象却输出一堆看不懂的代码 <__main__.MyCls object at 0x105732250> 。
不是因为系统做了特殊定制,而是 Python 中有一类特殊的方法,在某些特定的场合会自动调用。如,在字符串类 str 中定义了 __add__ 方法后,当代码遇到字符串相加 'ab' + 'cd' 时,就会自动调用 __add__ 方法完成字符串拼接。
因为这类特殊方法的方法名都是以双下划线开始和结束,所以又被称为双下方法。
Python 中的双下方法很多,今天我们对它做个详解。
Python中的双下方法
1. init方法
__init__ 的方法是很多人接触的第一个 双下方法 。
class?A: ????def?__init__(self,?a): ????????self.a?=?a
当调用 A() 实例化对象的时候, __init__ 方法会被自动调用,完成对象的初始化。
2. 运算符的双下方法
在类中定义运算符相关的 双下方法 ,可以直接在类对象上做加减乘除、比较等操作。
这里,定义一个尺子类 Rule ,它包含一个属性 r_len 代表尺子的长度。
class?Rule: ????def?__init__(self,?r_len): ????????self.r_len?=?r_len
2.1 比较运算符
如果想按照尺子的长度对不同的尺子做比较,需要在 Rule 类中定义比较运算符。
class?Rule: ????def?__init__(self,?r_len): ????????self.r_len?=?r_len ????#?<?运算符 ????def?__lt__(self,?other): ????????return?self.r_len?<?other.r_len ????#?<=?运算符 ????def?__le__(self,?other): ????????return?self.r_len?<=?other.r_len ????#?>?运算符 ????def?__gt__(self,?other): ????????return?self.r_len?>?other.r_len ????#?>=?运算符 ????def?__ge__(self,?other): ????????return?self.r_len?>=?other.r_len
这里定义了 < 、 <= 、 > 和 >= 四个比较运算符,这样就可以用下面的代码比较 Rule 对象了。
rule1?=?Rule(10) rule2?=?Rule(5) print(rule1?>?rule2)??#?True print(rule1?>=?rule2)??#?True print(rule1?<?rule2)??#?False print(rule1?<=?rule2)??#?False
当用 > 比较 rule1 和 rule2 的时候, rule1 对象会自动调用 __gt__ 方法,并将 rule2 对象传给 other 参数,完成比较。
下面是比较运算符的双下方法
比较运算符双下方法
2.2 算术运算符
可以支持类对象加减乘除。
def?__add__(self,?other): ????return?Rule(self.r_len?+?other.r_len)
这里定义了 __add__ 方法,对应的是 + 运算符,他会把两个尺子的长度相加,并生成新的尺子。
rule1?=?Rule(10) rule2?=?Rule(5) rule3?=?rule1?+?rule2
下面是算术运算符的双下方法
2.3 反向算术运算符
它支持其他类型的变量与 Rule 类相加。以 __radd__ 方法为例
def?__radd__(self,?other): ????return?self.r_len?+?other
rule1?=?Rule(10) rule2?=?10?+?rule1
程序执行 10 + rule1 时,会尝试调用 int 类的 __add__ 但 int 类类没有定义与 Rule 类对象相加的方法,所以程序会调用 + 号右边对象 rule1 的 __radd__ 方法,并把 10 传给 other 参数。
所以这种运算符又叫右加运算符。它所支持的运算符与上面的算术运算符一样,方法名前加 r 即可。
2.4 增量赋值运算符
增量赋值运算符是 += 、 -= 、 *= 、 /= 等。
def?__iadd__(self,?other): ????self.r_len?+=?other ????return?self
rule1?=?Rule(10) rule1?+=?5
除了 __divmod__ 方法,其他的跟算数运算符一样,方面名前都加i。
2.4 位运算符
这部分支持按二进制进行取反、移位和与或非等运算。由于 Rule 类不涉及位运算,所以我们换一个例子。
定义二进制字符串的类 BinStr ,包含 bin_str 属性,表示二进制字符串。
class?BinStr: ????def?__init__(self,?bin_str): ????????self.bin_str?=?bin_str
x?=?BinStr('1010')??#创建二进制字符串对象 print(x.bin_str)?#?1010
给 BinStr 定义一个取反运算符 ~
#?~?运算符 def?__invert__(self): ????inverted_bin_str?=?''.join(['1'?if?i?==?'0'?else?'0'?for?i?in?self.bin_str]) ????return?BinStr(inverted_bin_str)
__invert__ 方法中,遍历 bin_str 字符串,将每位取反,并返回一个新的 BinStr 类对象。
x?=?BinStr('1011') invert_x?=?~x print(invert_x.bin_str)?#?0100
下面是位运算符的双下方法
这部分也支持反向位运算符和增量赋值位运算符,规则跟算数运算符一样,这里就不再赘述。
3.字符串表示
这部分涉及两个双下方法 __repr__ 和 __format__ ,在某些特殊场景,如 print ,会自动调用,将对象转成字符串。
还是以 BinStr 为例,先写 __repr__ 方法。
def?__repr__(self): ????decimal?=?int('0b'+self.bin_str,?2) ????return?f'二进制字符串:{self.bin_str},对应的十进制数字:{decimal}'
x?=?BinStr('1011') print(x) #?输出:二进制字符串:1011,对应的十进制数字:11
当程序执行 print(x) 时,会自动调用 __repr__ 方法,获取对象 x 对应的字符串。
再写 __format__ 方法,它也是将对象格式化为字符串。
def?__format__(self,?format_spec): ????return?format_spec?%?self.bin_str
print('{0:二进制字符串:%s}'.format(x)) #?输出:二进制字符串:1011
当 .format 方法的前面字符串里包含 0: 时,就会自动调用 __format__ 方法,并将字符串传给 format_spec 参数。
4.数值转换
调用 int(obj) 、 float(obj) 等方法,可以将对象转成相对应数据类型的数据。
def?__int__(self): ????return?int('0b'+self.bin_str,?2)
x?=?BinStr('1011') print(int(x))
当调用 int(x) 时,会自动调用 __int__ 方法,将二进制字符串转成十进制数字。
数值转换除了上面的两个外,还有 __abs__ 、 __bool__ 、 __complex__ 、 __hash__ 、 __index__ 和 __str__ 。
__str__ 和 __repr__ 一样,在 print 时都会被自动调用,但 __str__ 优先级更高。
5.集合相关的双下方法
这部分可以像集合那样,定义对象长度、获取某个位置元素、切片等方法。
以 __len__ 和 __getitem__ 为例
def?__len__(self): ????return?len(self.bin_str) def?__getitem__(self,?item): ????return?self.bin_str[item]
x?=?BinStr('1011') print(len(x))??#?4 print(x[0])??#?1 print(x[0:3])??#?101
len(x) 会自动调用 __len__ 返回对象的长度。
通过 [] 方式获取对象的元素时,会自动调用 __getitem__ 方法,并将切片对象传给 item 参数,即可以获取单个元素,还可以获取切片。
集合相关的双下方法还包括 __setitem__ 、 __delitem__ 和 __contains__ 。
6.迭代相关的双下方法
可以在对象上使用 for-in 遍历。
def?__iter__(self): ????self.cur_i?=?-1 ????return?self def?__next__(self): ????self.cur_i?+=?1 ????if?self.cur_i?>=?len(self.bin_str): ????????raise?StopIteration()??#?退出迭代 ????return?self.bin_str[self.cur_i]
x?=?BinStr('1011') for?i?in?x: ????print(i)
当在 x 上使用 for-in 循环时,会先调用 __iter__ 方法将游标 cur_i 置为初始值 -1 ,然后不断调用 __next__ 方法遍历 self.bin_str 中的每一位。
这部分还有一个 __reversed__ 方法用来反转对象。
def?__reversed__(self): ????return?BinStr(''.join(list(reversed(self.bin_str))))
x?=?BinStr('1011') reversed_x?=?reversed(x) print(reversed_x) #?输出:二进制字符串:1101,对应的十进制数字:13
7.类相关的双下方法
做 web 开发的朋友,用类相关的双下方法会更多一些。
7.1 实例的创建和销毁
实例的创建是 __new__ 和 __init__ 方法,实例的销毁是 __del__ 方法。
__new__ 的调用早于 __init__ ,它的作用是创建对象的实例(内存开辟一段空间),而后才将该实例传给 __init__ 方法,完成实例的初始化。
由于 __new__ 是类静态方法,因此它可以控制对象的创建,从而实现 单例模式 。
__del__ 方法在实例销毁时,被自动调用,可以用来做一些清理工作和资源释放的工作。
7.2 属性管理
类属性的访问和设置。包括 __getattr__ 、 __getattribute__ 、 __setattr__ 和 __delattr__ 方法。
__getattr__ 和 __getattribute__ 的区别是,当访问类属性时,无论属性存不存在都会调用 __getattribute__ 方法,只有当属性不存在时才会调用 __getattr__ 方法。
7.3 属性描述符
控制属性的访问,一般用于把属性的取值控制在合理范围内。包括 __get__ 、 __set__ 和 __delete__ 方法。
class?XValidation: ????def?__get__(self,?instance,?owner): ????????return?self.x ????def?__set__(self,?instance,?value): ????????if?0?<=?value?<=?100: ????????????self.x?=?value ????????else: ????????????raise?Exception('x不能小于0,不能大于100') ????def?__delete__(self,?instance): ????????print('删除属性') class?MyCls: ????x?=?XValidation() ????def?__init__(self,?n): ????????self.x?=?n obj?=?MyCls(10) obj.x?=?101 print(obj.x)?#?抛异常:Exception: x不能小于0,不能大于100
上述例子,通过类属性描述符,可以将属性x的取值控制在 [0, 100] 之前,防止不合法的取值。
8.总结
虽然上面介绍的不是所有的双下方法,但也算是绝大多数了。
虽然双下方法里可以编写任意代码,但大家尽量编写与方法要求一样的代码。如,在 __add__ 方法实现的不是对象相加而是相减,虽然也能运行,但这样会造成很大困惑,不利于代码维护。
到此这篇关于一文带你了解Python中的双下方法的文章就介绍到这了,更多相关Python双下方法内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!
查看更多关于一文带你了解Python中的双下方法的详细内容...