欢迎访问的个人博客,博客地址为 https://haibei.online/
〇、魔术方法定义
- 在python语法中,往往你会看到有的名称前面和后面都加上了双下划线,由这些名字组成的集合所包含的方法就叫做魔法方法,也叫做特殊方法。
- python的魔术方法非常强大,然而随之而来的则是责任。了解正确的方法去使用非常重要!
一、总述
1、class类的老大哥 __new__
- 之所以称之为老大哥,是因为
__new__
在类执行时第一个被调用,而不是__init__
- 如果
__new__
返回值为None,那么__init__
根本不会被调用。 - 举个例子
class A(object):
def __new__(cls):
print("__new__方法被执行")
return super().__new__(cls)
def __init__(self):
print("__init__方法被执行")
a = A()
结果如下:
__new__方法被执行
__init__方法被执行
2、实例销毁: __del__
__del__(self)
:当一个实例被销毁的时候调用的方法
3、类语法糖: __call__
__call__(self[, args...])
允许一个类的实例像函数一样被调用: x(a, b)
调用 x.__call__(a, b)
3.1、演示代码
class A(object):
def __init__(self, name):
self.name = name
print(f"{name}被创造出来了了")
def __call__(self, *args, **kwargs):
"""允许一个类的实例像函数一样被调用"""
print(f"{self.name}打了个响指,一共{len(args)}名超级英雄消失,他们分别是{'、'.join(args)}等。")
def __del__(self):
"""程序结束(销毁)后,会自动执行"""
print(f"{self.name}被销毁了")
if __name__ == '__main__':
a = A("灭霸")
a("蜘蛛侠", "幻世", "黑豹", "奇异博士")
结果如下:
灭霸被创造出来了了
灭霸打了个响指,一共4名超级英雄消失,他们分别是蜘蛛侠、幻世、黑豹、奇异博士等。
灭霸被销毁了
二、调用系统方法
语法 | 说明 | 符号 |
---|---|---|
__len__(self) | 定义当被 len 调用时的行为 | len() |
__repr__(self) | 定义当被 repr 调用时的行为 | repr() |
__str__(self) | 定义当被 str 调用时的行为 | str() |
__bytes__(self) | 定义当被 bytes 调用时的行为 | bytes() |
__hash__(self) | 定义当被 hash 调用时的行为 | hash() |
__bool__(self) | 定义当被 bool 调用时的行为 | bool() |
__format__(self, format_spec) | 定义当被 format 调用时的行为 | format() |
1、 __repr__
和 __str__
的区别
-
当一个类中没有使用
__repr__
和__str__
这两个魔法函数,print()
直接打印实例会返回类名和内存地址 -
__repr__
和__str__
这两个方法都是用于显示的,__str__
是面向用户的,而__repr__
面向程序员。 -
打印操作会首先尝试
__str__
,它通常应该返回一个友好的显示。 -
在交互环境下,
__repr__
会生效,而__str__
不会起作用。直接执行实例变量名即可打印,无需print()
。
2、参考代码
class A(object):
"""对照组:没有使用__str__,打印结果为类型名及其内存地址"""
def __init__(self, info):
self.info = info
class B(object):
"""标准组:使用__str__了,打印结果为__str__魔法函数的返回值"""
def __init__(self, info):
self.info = info
def __str__(self):
return self.info + f"我是{__class__.__name__}类,我用了__str__的魔法函数,真香!"
if __name__ == '__main__':
demo_message = "真氪金定律:程序员的能力与发量成反比!快拔头发吧!"
a = A(demo_message)
b = B(demo_message)
print(a)
print(b)
显示结果如下:
<__main__.A object at 0x1097bf438>
真氪金定律:程序员的能力与发量成反比!快拔头发吧!我是B类,我用了__str__的魔法函数,真香!
2.1、什么是交互环境
交互环境的典型特点是写完一行,执行一行。非交互环境要一次执行所有程序
- 直接在Terminal中启动python,就是交互环境
- Jupyter Notebook和Lab都是交互环境
- Pycharm中的python Console是交互环境
- Anaconda Spyder中提供的Console也是交互环境
2.2、在交互环境中 __repr__
和 __str__
代码如下:
[外链图片转存中…(img-2LbfSCdz-1572167290870)]
- 可以看出,5行中直接执行
a
因为定义了__repr__
,在交互环境下是可以正确显示的; - 而在7行中,直接执行
b
是无法正确显示的。
三、有关属性
语法 | 说明 |
---|---|
__getattr__(self, name) | 定义当用户试图获取一个不存在的属性时的行为 |
__getattribute__(self, name) | 定义当该类的属性被访问时的行为 |
__setattr__(self, name, value) | 定义当一个属性被设置时的行为 |
__delattr__(self, name) | 定义当一个属性被删除时的行为 |
__dir__(self) | 定义当 dir() 被调用时的行为 |
__get__(self, instance, owner) | 定义当描述符的值被取得时的行为 |
__set__(self, instance, value) | 定义当描述符的值被改变时的行为 |
__delete__(self, instance) | 定义当描述符的值被删除时的行为 |
1、 __getattr__
和 __setattr__
存在的问题
__getattr__
和__setattr__
这两个方法__init__
中变量赋值事,就会默认被创建。- 举个例子:传统赋值方式是
self.a="a"
,__getattr__
和__setattr__
就会默认创建出来。 - 所以当在
__getattr__
中使用getattr()
或者self.xxx
, 程序会陷入无限递归。 - 防止递归解决方式有两种,但原理相同。使用超类赋值或者通过超类进行访问
- 超类赋值(新式类):super().setattr(“xxx”, xxx)
- 超类访问(新式类):super().getattr(“xxx”)
2、演示代码
代码功能:伪造一个类,使其具有与
dict
类型变量相同的功能;
同时,该类还具备通过’点’来访问dict的方法
class A(object):
"""伪造字典"""
def __init__(self, **kwargs):
super().__setattr__("dict_like", kwargs) # 超类赋值,防止递归
def __getattr__(self, name):
"""可以调用dict类的系统方法"""
print("__getattr__被执行:", end="")
if name in self.dict_like:
print(f"\t访问{name}变量", end="")
return self.dict_like[name]
else:
print(f"\t访问{name}方法", end="")
return getattr(self.dict_like, name)
def __setattr__(self, key, value):
"""可以通过'点'的方式,设置字典内容"""
print("__setattr__被执行:", end="")
self.dict_like[key] = value
def __delattr__(self, item):
print("__delattr__被执行:", end="")
del self.dict_like[item]
def __len__(self):
print("__len__被执行:", end="")
return len(self.dict_like)
def __str__(self):
print("__str__被执行:", end="")
return str(self.dict_like)
if __name__ == '__main__':
a = A(a=1, b=2, c=3, d=4, e=5, f=6, g=7) # 实例化
print(a) # 打印
print(len(a)) # 获取长度
a.h = 8 # 字典赋值
print(a) # 打印赋值结果
print("\t|\t访问b元素:", a.b) # 访问b元素
print(list(a.items())) # 调用dict的系统方法
del a.f # 删除字典元素
print(a) # 打印删除后的结果
结果如下:
__str__被执行:{'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6, 'g': 7}
__len__被执行:7
__setattr__被执行:__str__被执行:{'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6, 'g': 7, 'h': 8}
__getattr__被执行:访问b变量 | 访问b元素: 2
__getattr__被执行:访问items方法[('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5), ('f', 6), ('g', 7), ('h', 8)]
__delattr__被执行:__str__被执行:{'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'g': 7, 'h': 8}
四、比较操作符
语法 | 说明 | 符号 |
---|---|---|
__lt__(self, other) | 定义小于号的行为 | x < y |
__le__(self, other) | 定义小于等于号的行为 | x <= y |
__eq__(self, other) | 定义等于号的行为 | x == y |
__ne__(self, other) | 定义不等号的行为 | x != y |
__gt__(self, other) | 定义大于号的行为 | x > y |
__ge__(self, other) | 定义大于等于号的行为 | x >= y |
五、算数运算符
可以让类使用算数预算符进行操作
1、标准运算符
说明 | 代码 | 符号 | 反运算 | 增强运算 |
---|---|---|---|---|
加法 | __add__(self, other) | + | √ | += |
减法 | __sub__(self, other) | - | √ | -= |
乘法 | __mul__(self, other) | * | √ | *= |
除法 | __truediv__(self, other) | / | √ | /= |
整除 | __floordiv__(self, other) | // | √ | //= |
模运算 | __mod__(self, other) | % | √ | %= |
divmod操作 | __divmod__(self, other) | divmod() | √ | 无 |
次方 | __pow__(self, other[, modulo]) | power()或 ** | √ | **= |
左位移 | __lshift__(self, other) | << | √ | <<= |
右位移 | __rshift__(self, other) | >> | √ | >>= |
按位与 | __and__(self, other) | & | √ | &= |
按位异或 | __xor__(self, other) | ^ | √ | ^= |
按位或 | __or__(self, other) | | | √ | |= |
2、反运算
- 在标准运算符的前面加上“r”就是反运算
- 假设
a
和b
是两个实例,就a而言,a+b
为标准预算,因为a
在前边;b+a
就是反运算,因为a
在后面。 - 如果
a
想使用反运算,就需要定义__radd__
,
3、增量赋值运算
- 在标准运算符的前面加上“i”就是增量赋值运算
4、代码实例
4.1、3种运算符的区别
class A(object):
def __add__(self, other):
print("A:加法的标准运算")
def __radd__(self, other):
print("A:加法的反运算")
def __iadd__(self, other):
print("A:加法的增强赋值运算")
class B:
pass
a=A()
b=B()
a+b
b+a
a+=b
输出结果:
A:加法的标准运算
A:加法的反运算
A:加法的增强赋值运算
4.2、伪造list类,使其具备运算符功能
class A(object):
"""伪造list,通过运算符号,完成增减数据"""
def __init__(self, *args):
self.list_like = list(args) # args默认为元组类型,需要转换成为list
def __add__(self, other):
print("__add__被执行:", end="")
self.list_like.append(other)
return self
def __radd__(self, other):
print("__radd__被执行:", end="")
self.list_like = [other] + self.list_like
return self
def __iadd__(self, other):
print("__iadd__被执行:", end="")
self.list_like.append(other)
return self
def __mul__(self, other):
print("__mul__被执行:", end="")
self.list_like = list(map(lambda x: x * other, self.list_like))
return self
def __len__(self):
return len(self.list_like)
def __str__(self):
return str(self.list_like)
if __name__ == '__main__':
a = A(1, 2, 3, 5, 6, 7)
a = a + 10 # 向list中最后一位添加数据10
print(a)
a = 0 + a # 向list中第一位添加数据0
print(a)
a += 100 # 向list中最后一位添加数据100
print(a)
a = a * 6 # 向list中每一位乘以6
print(a)
输出结果:
__add__被执行:[1, 2, 3, 5, 6, 7, 10]
__radd__被执行:[0, 1, 2, 3, 5, 6, 7, 10]
__iadd__被执行:[0, 1, 2, 3, 5, 6, 7, 10, 100]
__mul__被执行:[0, 6, 12, 18, 30, 36, 42, 60, 600]
六、一元操作符
语法 | 说明 | 符号 |
---|---|---|
__neg__(self) | 定义正号的行为 | -x |
__pos__(self) | 定义负号的行为 | +x |
__abs__(self) | 定义绝对值的行为 | abs() |
__invert__(self) | 定义按位求反的行为 | ~x |
1、示例代码
class A(object):
"""伪造list,具备算法运算符功能"""
def __init__(self, *args):
self.list_like = list(args) # args默认为元组类型,需要转换成为list
def __neg__(self):
print("__neg__被执行:", end="")
self.list_like = list(map(lambda x: -x, self.list_like))
return self
def __abs__(self):
print("__abs__被执行:", end="")
self.list_like = list(map(lambda x: abs(x), self.list_like))
return self
def __invert__(self):
print("__invert__被执行:", end="")
self.list_like = list(map(lambda x: ~x, self.list_like))
return self
def __len__(self):
return len(self.list_like)
def __str__(self):
return str(self.list_like)
if __name__ == '__main__':
a = A(5, 3, 16, 14, 60, 3)
a = -a # 对list中每一位数据取负值
print(a)
a = abs(a) # 对list中每一位数据绝对值
print(a)
a = ~a # 对list中每一位数据取反
print(a)
输出结果
__neg__被执行:[-5, -3, -16, -14, -60, -3]
__abs__被执行:[5, 3, 16, 14, 60, 3]
__invert__被执行:[-6, -4, -17, -15, -61, -4]
七、类型转换
语法 | 说明 |
---|---|
__complex__(self) | 定义当被 complex() 调用时的行为(需要返回恰当的值) |
__int__(self) | 定义当被 int() 调用时的行为(需要返回恰当的值) |
__float__(self) | 定义当被 float() 调用时的行为(需要返回恰当的值) |
__round__(self[, n]) | 定义当被 round() 调用时的行为(需要返回恰当的值) |
__index__(self) | 当对象是被应用在切片表达式中时,实现整形强制转换;如果你定义了一个可能在切片时用到的定制的数值型, 你应该定义 index。如果 index 被定义,则 int 也需要被定义,且返回相同的值 |
八、上下文管理(with 语句)
演示代码如下:
with open("./1.txt","r",encoding="utf-8") as f:
data=f.readlines()
语法 | 说明 |
---|---|
__enter__ | 1. 定义当使用 with 语句时的初始化行为; 2. __enter__ 的返回值被 with 语句的目标或者 as 后的名字绑定 |
__exit__ | 1. 定义当一个代码块被执行或者终止后上下文管理器应该做什么 2. 一般被用来处理异常,清除工作或者做一些代码块执行完毕之后的日常工作 |
九、容器类型
语法 | 说明 |
---|---|
__len__(self) | 定义当被 len() 调用时的行为(返回容器中元素的个数) |
__getitem__(self, key) | 定义获取容器中指定元素的行为,相当于 self[key] |
__setitem__(self, key, value) | 定义设置容器中指定元素的行为,相当于 self[key] = value |
__delitem__(self, key) | 定义删除容器中指定元素的行为,相当于 del self[key] |
__iter__(self) | 定义当迭代容器中的元素的行为 |
__reversed__(self) | 定义当被 reversed() 调用时的行为 |
__contains__(self, item) | 定义当使用成员测试运算符( in 或 not in )时的行为 |
1、 示例代码
class Avenger(object):
"""复仇者联盟"""
def __init__(self, **kwargs):
self.members = kwargs
self.names = list(kwargs.keys())
self.index = 0
def __getitem__(self, item):
fighting_num = self.members[item]
print("__getitem__被执行:", f"正在读取复仇者【{item}】的信息,其战斗力为{fighting_num}")
return fighting_num
def __setitem__(self, key, value):
print("__setitem__被执行:", f"新增复仇者【{key}】,其战斗力为{value}")
self.members["key"] = value
def __delitem__(self, key):
print("__delitem__被执行:", f"复仇者【{key}】已阵亡!")
del self.members["key"]
def __iter__(self):
print("__iter__被执行")
return self
def __next__(self):
if self.index == len(self.names) - 1:
raise StopIteration
else:
self.index += 1
return self.names[self.index]
def __gt__(self, other):
print("__gt__被执行", f"战斗力大于60的复仇者:{other}")
members = filter(lambda x: x[-1] > other, self.members.items())
return list(members)
def __len__(self):
return len(self.members)
def __str__(self):
power_info = map(lambda x: "{0}的战斗力为 {1}".format(*x), self.members.items())
return "\n".join(power_info)
if __name__ == '__main__':
a = Avenger(钢铁侠=106, 雷神=110, 浩克=120, 奇异博士=90, 蜘蛛侠=80, 美国队长=50, 幻世=60)
print(len(a))
print(f"======== 雷神的战斗力:{a['雷神']} ========")
a["古鲁姆"] = 15.6
del a["幻世"]
for i in a:
print(i)
print(a > 60)
结果如下:
7
__getitem__被执行: 正在读取复仇者【雷神】的信息,其战斗力为110
======== 雷神的战斗力:110 ========
__setitem__被执行: 新增复仇者【古鲁姆】,其战斗力为15.6
__delitem__被执行: 复仇者【幻世】已阵亡!
__iter__被执行
雷神
浩克
奇异博士
蜘蛛侠
美国队长
幻世
__gt__被执行 战斗力大于60的复仇者:60
[('钢铁侠', 106), ('雷神', 110), ('浩克', 120), ('奇异博士', 90), ('蜘蛛侠', 80)]