目录
一、属性相关的魔法方法
(1) __getattribute__
(2)__getattr__
(3)__setattr__
(4)__delattr__
二,容器魔法方法
(1)__len__ ,__getitem__, __setitem__, __delItem__
(2) __iter__, __next__
一、属性相关的魔法方法
1.1 类别
__getattr__(self, name)__setattr__(self, name)__getattribute__(self, name)__delattr__(self, name) __dir__(self, name)
1.2 属性访问顺序
1. 调用__getattribute__
2. 调用数据描述符 (同时具备三个魔术方法的描述符)
3. 调用当前对象的所属成员
4. 调用类的所属成员
5. 调用非数据描述符(没有同时具备三个魔术方法的类,他们两者的区别就是访问顺序的高低)
6. 调用父类的所属成员
7. 调用__getattr__
(1) __getattribute__
触发时机: 只要访问属性就会被触发,不管对象的属性存在与否。作用: 在给予对象数据时可以对对象的属性进行处理。参数:self:当前对象, item:接受访问对象成员的字符串。返回值:有,不设定则返回None。注意事项:在当前魔法方法中禁止使用 self.属性 的方式访问属性,否则会造成递归重复,必须借助object.__getattribute__来获取对象成员。
执行代码如下图所示:
class Human:# 添加属性def __init__(self, name, sex, age):self.name = nameself.sex = sexself.age = age# 添加方法# 添加魔术方法def __getattribute__(self, item):print("属性已被处理")return f"{self.name}先生" # __getattribute__返回的数据才是现在能访问得到的值def eat(self):print("吃饭")def drink(self):print("喝酒")# 实例化对象ls = Human("李四", "男", 18)print(ls)# 访问对象的名称print(ls.name)
运行代码会报错:
File "H:\PycharmProjects\pythonProject1\Python学习\magic\magic. __getattribute__.py", line 20, in __getattribute__return f"{self.name}先生" # __getattribute__返回的数据才是现在能访问得到的值^^^^^^^^^[Previous line repeated 994 more times]File "H:\PycharmProjects\pythonProject1\Python学习\magic\magic. __getattribute__.py", line 19, in __getattribute__print("属性已被处理")RecursionError: maximum recursion depth exceeded while calling a Python object进程已结束,退出代码1
报错的原因是因为在 return f"{self.name}先生" 这行代码中也调用了对象的属性,而一旦调用属性就会执行 __getattribute__,之后又会调用return,触发了递归操作,造成大量的重复使得报错。
为了避免这种情况的发生,需要在调用对象属性的时候使用函数最底层的object来进行访问:
class Human:# 添加属性def __init__(self, name, sex, age):self.name = nameself.sex = sexself.age = age# 添加方法# 添加魔术方法def __getattribute__(self, item): # item接受的时访问属性的名称字符串而已print("属性已被处理") # 在这里使用最底层object来进行获取result = object.__getattribute__(self, item) # 此时就会将获取到的"name"赋值给result进行使用了# 隐藏用户名(让中间的名字为'*')return result[0] + "*" +result[-1] # __getattribute__返回的数据才是现在能访问得到的值def eat(self):print("吃饭")def drink(self):print("喝酒")# 实例化对象ls = Human("李四", "男", 18)print(ls)# 访问对象的名称print(ls.name)
运行代码:
H:\PycharmProjects\pythonProject1\venv\Scripts\python.exe "H:\PycharmProjects\pythonProject1\Python学习\magic\magic. __getattribute__.py" <__main__.Human object at 0x000001FDE5C71D10>属性已被处理李*四进程已结束,退出代码0
(2)__getattr__
1. 触发时机:访问不存在的对象成员的时候自动触发
2. 作用: 防止访问不存在成员的时候报错,为不存在的成员定义值
3. 参数:一个self接收当前对象,第二个参数接收访问成员的名称字符串
4. 返回值:有即返回,无就None
5. 注意事项:木有
class Human:# 添加属性def __init__(self, name, sex, age):self.name = nameself.sex = sexself.age = age# 添加魔法方法def __getattr__(self, item):print("__getattr__被触发")def eat(self):print("吃饭")def drink(self):print("喝酒")# 实例化对象ls = Human("李四", "男", 18)print(ls)# 访问对象的名称print(ls.name)
H:\PycharmProjects\pythonProject1\venv\Scripts\python.exe "H:\PycharmProjects\pythonProject1\Python学习\magic\magic. __getattribute__.py"
<__main__.Human object at 0x0000021A3DE51C10>
李四
进程已结束,退出代码0
可见__getattr__没有触发:
class Human:# 添加属性def __init__(self, name, sex, age):self.name = nameself.sex = sexself.age = age# 添加魔法方法def __getattr__(self, item):print("__getattr__被触发")def eat(self):print("吃饭")def drink(self):print("喝酒")# 实例化对象ls = Human("李四", "男", 18)print(ls)# 访问对象的名称print(ls.name2)
H:\PycharmProjects\pythonProject1\venv\Scripts\python.exe "H:\PycharmProjects\pythonProject1\Python学习\magic\magic. __getattribute__.py"
<__main__.Human object at 0x0000028688631C50>
__getattr__被触发
None
进程已结束,退出代码0
此时因为没有name2这个属性,所以__getattr__触发,同时__getattr__可以通过返回值给未定义的属性赋值
class Human:# 添加属性def __init__(self, name, sex, age):self.name = nameself.sex = sexself.age = age# 添加魔法方法def __getattr__(self, item):print("__getattr__被触发")return "定义值"def eat(self):print("吃饭")def drink(self):print("喝酒")# 实例化对象ls = Human("李四", "男", 18)print(ls)# 访问对象的名称print(ls.name2)运行结果:H:\PycharmProjects\pythonProject1\venv\Scripts\python.exe "H:\PycharmProjects\pythonProject1\Python学习\magic\magic. __getattribute__.py" <__main__.Human object at 0x00000234B3711C90>__getattr__被触发定义值进程已结束,退出代码0
(3)__setattr__
触发时机:添加对象属性,或者修改对象属性的时候触发
作用:可以限制或者管理对象成员的添加与修改操作
参数:一个self接收当前对象,第二个参数接收访问成员的名称字符串
返回值:可有可无
注意事项:在当前魔术方法中禁止使用:当前对象.成员名 = 值的方式.会触发递归操作
class Human:# 添加属性def __init__(self, name, sex, age):self.name = nameself.sex = sexself.age = age# 添加魔法方法def __setattr__(self, key, value): # 一旦发生属性设置,就会被调用!print("__setattr__被调用!")def eat(self):print("吃饭")def drink(self):print("喝酒")# 实例化对象ls = Human("李四", "男", 18)print(ls.name)运行结果:H:\PycharmProjects\pythonProject1\venv\Scripts\python.exe "H:\PycharmProjects\pythonProject1\Python学习\magic\magic. __getattribute__.py" __setattr__被调用!__setattr__被调用!__setattr__被调用!Traceback (most recent call last):File "H:\PycharmProjects\pythonProject1\Python学习\magic\magic. __getattribute__.py", line 22, in <module>print(ls.name)^^^^^^^AttributeError: 'Human' object has no attribute 'name'进程已结束,退出代码1
从上图中我们可以看出报错的结果是因为 "Hunman" 未能定义属性 "name" ,这是因为在初始化函数__init__中,定义 self.属性 也算是设置属性,所以都调用了__setattr__,才会出现三个 __setattr__函数被调用,这也导致了__init__函数设置属性时被拦截,所以未定义"name"等属性
__setattr__(self, key, value)中三个参数中key实例对象的属性的名称,一般称为attrname,value时实例对象的真实值
class Human:# 添加属性def __init__(self, name, sex, age, wife):self.name = nameself.sex = sexself.age = ageself.wife = wife# 添加魔法方法def __setattr__(self, key, value): # 一旦发生属性设置,就会被调用!if key == "wife":super().__setattr__("wife", "张三") # 使输入的wife不管为什么,最后输出都为"张三"# self.__dict__[wife] = "张三" # 这个语句与上面的语句等效else:super().__setattr__(key, value) # 不是wife属性的直接继承__setattr__,保持参数不变# self.__dict__[key] = value # 同理def eat(self):print("吃饭")def drink(self):print("喝酒")# 实例化对象ls = Human("李四", "男", 18, "张三")print("修改前:", ls.__dict__)print("前:", ls.wife)# 修改参数ls = Human("李老四", "男", 68, "王五")print("修改后:", ls.__dict__)print("后:", ls.wife)运行结果:H:\PycharmProjects\pythonProject1\venv\Scripts\python.exe "H:\PycharmProjects\pythonProject1\Python学习\magic\magic. __getattribute__.py" 修改前: {'name': '李四', 'sex': '男', 'age': 18, 'wife': '张三'}前: 张三修改后: {'name': '李老四', 'sex': '男', 'age': 68, 'wife': '张三'}后: 张三进程已结束,退出代码0
从上图中我们可以看出,__setattr__可以在修改参数时进行自定义操作,比如上图,即使我们修改了实例化对象的参数wife,欲使其变成 "王五",但是通过结果我们发现,wife并没有发生变化,这就是__setattr__的作用。
(4)__delattr__
通过下面的例子我们可以看出,__delattr__和之前学习的一样,都可以对删除操作进行拦截并且自重构删除操作,但是还是要注意不能使用 self.属性
class Human:# 添加属性def __init__(self, name, sex, age, wife):self.name = nameself.sex = sexself.age = ageself.wife = wife# 添加魔法方法def __setattr__(self, key, value): # 一旦发生属性设置,就会被调用!if key == "wife":super().__setattr__("wife", "张三")else:super().__setattr__(key, value)def __delattr__(self, item): # 自定义删除方式if item == "wife": # 只要删除"wife",就会打印下面,而且不会删除"wife"print("你小子敢删我老婆,我砍死你!")else:del self.__dict__[item] # 删除别的属性就执行删除操作# 实例化对象ls = Human("李四", "男", 18, "张三")print("删除前:", ls.__dict__)# 删除参数del ls.nameprint("删除后:", ls.__dict__)print("只要没动我老婆就好")del ls.wifeprint("删除后:", ls.__dict__)运行结果:H:\PycharmProjects\pythonProject1\venv\Scripts\python.exe "H:\PycharmProjects\pythonProject1\Python学习\magic\magic. __getattribute__.py" 删除前: {'name': '李四', 'sex': '男', 'age': 18, 'wife': '张三'}删除后: {'sex': '男', 'age': 18, 'wife': '张三'}只要没动我老婆就好你小子敢删我老婆,我砍死你!删除后: {'sex': '男', 'age': 18, 'wife': '张三'}进程已结束,退出代码0
源视频:/video/BV11K4y1C7TB/?spm_id_from=333.337.search-card.all.click&vd_source=62c17a2b0f761d6d01138d268a216191
二,容器魔法方法
(1)__len__ ,__getitem__, __setitem__, __delItem__
可以定义一个容器的类,但是使用这个类所创建的实例对象,是不能直接被访问即操作的,如下图所示:
class DictDemo:def __init__(self, key, value):self.Dict = {}self.Dict[key] = value# def __getitem__(self, item):#print("getitem is working")#return 1## def __setitem__(self, key, value):#print("__setitem__ is working")#self.Dict[key] = value## def __delitem__(self, key):#print("__delitem__ is working")#del self.Dict[key]## def __len__(self):#print("__len__ is working")#return len(self.Dict)d = DictDemo("one", 1)print(d.Dict)print(d.Dict["one"])# print(len(d))print(d["one"])# d["two"] = 2# print(d.Dict)# del d["two"]# print(d.Dict)运行结果:H:\PycharmProjects\pythonProject1\venv\Scripts\python.exe H:\PycharmProjects\pythonProject1\Temporaryfiles.py {'one': 1}1Traceback (most recent call last):File "H:\PycharmProjects\pythonProject1\Temporaryfiles.py", line 28, in <module>print(d["one"])~^^^^^^^TypeError: 'DictDemo' object is not subscriptable进程已结束,退出代码1
定义了一个字典,没法直接进行访问,所以现在可以引入__getitem__来进行获取,__setitem__来对实例化的对象进行设置,__delitem__来对实例对象进行删除,__len__可以获得实例对象的长度如下图
class DictDemo:def __init__(self, key, value):self.Dict = {}self.Dict[key] = valuedef __getitem__(self, item):print("getitem is working")return self.Dict[item]def __setitem__(self, key, value):print("__setitem__ is working")self.Dict[key] = valuedef __delitem__(self, key):print("__delitem__ is working")del self.Dict[key]def __len__(self):print("__len__ is working")return len(self.Dict)d = DictDemo("one", 1)print(d.Dict)print(d.Dict["one"])print(len(d))print(d["one"])d["two"] = 2print(d.Dict)print(d["two"])print(len(d))del d["two"]print(d.Dict)print(len(d))运行结果:H:\PycharmProjects\pythonProject1\venv\Scripts\python.exe H:\PycharmProjects\pythonProject1\Temporaryfiles.py {'one': 1}1__len__ is working1getitem is working1__setitem__ is working{'one': 1, 'two': 2}getitem is working2__len__ is working2__delitem__ is working{'one': 1}__len__ is working1进程已结束,退出代码0
只要实例对象发生获取操作就会触发__getitem__,从而被__getitem__拦截,其他函数同理!
(2) __iter__, __next__
在实例对象进行迭代操作时触发的魔法方法,__iter__的作用时是将实例对象转变为一个迭代对象,不然的话不能进行__next__操作,__next__就相当于while循环了,注意一定要终止循环,不然被会一直循环下去
time = 0class Double:def __init__(self, start, stop):self.value = start - 1self.stop = stopdef __iter__(self):return selfdef __next__(self):global timeif self.value == self.stop:raise StopIterationself.value += 1time += 1return self.value * 2d = Double(1, 5)for i in d:print(i, end=" ")print("迭代次数:", time)运行结果:H:\PycharmProjects\pythonProject1\venv\Scripts\python.exe H:\PycharmProjects\pythonProject1\Temporaryfiles.py 2 4 6 8 10 迭代次数: 5进程已结束,退出代码0
三、成员关系检测魔法方法
这个魔法方法主要是__cintains__(self, item), 由 in 或 not in 触发,主要用来检测成员是否在实例对象中。这里还要介绍一个叫做代偿的概念,代偿就是在查找不到__cintains__(self, item)时,就会找其他函数来代替。
class C:def __init__(self, data):self.data = data# 优先级:1def __contains__(self, item): # 这里的item就是下面的 3 和6print(item)print("__contains__ is working")return item in self.data# 优先级:2def __iter__(self):# print("__iter__ is working")print("开始", end="->")self.i = 0return selfdef __next__(self):# print("__next__ is working")print("正在查找", end="->")if self.i == len(self.data):raise StopIterationitem = self.data[self.i]self.i += 1return item # 因为这里返回的是一个非零的参数,所以print的结果就是True# 优先级:3def __getitem__(self, item):print("正在查找", end="->")return self.data[item] # 因为这里返回的是一个非零的参数,所以print的结果就是Truec = C([1, 2, 3, 4, 5])print(3 in c)print(6 in c)运行结果(优先级1时):H:\PycharmProjects\pythonProject1\venv\Scripts\python.exe H:\PycharmProjects\pythonProject1\Temporaryfiles.py 3__contains__ is workingTrue6__contains__ is workingFalse进程已结束,退出代码0运行结果(优先级2时):H:\PycharmProjects\pythonProject1\venv\Scripts\python.exe H:\PycharmProjects\pythonProject1\Temporaryfiles.py 开始->正在查找->正在查找->正在查找->True开始->正在查找->正在查找->正在查找->正在查找->正在查找->正在查找->False进程已结束,退出代码0运行结果(优先级3时):H:\PycharmProjects\pythonProject1\venv\Scripts\python.exe H:\PycharmProjects\pythonProject1\Temporaryfiles.py 正在查找->正在查找->正在查找->True正在查找->正在查找->正在查找->正在查找->正在查找->正在查找->False进程已结束,退出代码0
四、bool类型魔法方法及算数魔法方法
bool类型魔法方法由bool函数所触发,同样具有优先级
class D:# 优先级:1def __bool__(self):print("__bool__ is working")return True# # 优先级:2# def __init__(self, data):#self.data = data## def __len__(self):#print("__len__ is working")#return len(self.data) # 因为返回的是一个非零的参数,所以print的结果就是True# d = D([1, 2, 3])# print(bool(d))# print(bool([])) # 这里由于是空集,所以返回就是Falsea = D()print(bool(a))print(bool([]))运行结果:H:\PycharmProjects\pythonProject1\venv\Scripts\python.exe H:\PycharmProjects\pythonProject1\Temporaryfiles.py __bool__ is workingTrueFalse进程已结束,退出代码0
算法魔法方法简单,在比较时就会调用
class S(str):def __lt__(self, other):return len(self) < len(other)def __gt__(self, other):return len(self) > len(other)def __eq__(self, other):return len(self) == len(other)s1 = S("FishC")s2 = S("fishc")print(s1 < s2)print(s1 > s2)print(s1 == s2)print(s1 != s2) # 注意:这里运行的结果是True,这是由于没有定义__ne__(self, other),所以还是使用的普通的比较方式,# 即比较字符串的编码值,所以就不相等,同理的还有<=(__le__),>=(__ge__),如果不对其进行定义,那么他还是会执行本来的方法运行结果:H:\PycharmProjects\pythonProject1\venv\Scripts\python.exe H:\PycharmProjects\pythonProject1\Temporaryfiles.py FalseFalseTrueTrue进程已结束,退出代码0
__call__魔法方法就相当于将实例对象看作为一个函数,在进行这个操作时就会调用这个魔法方法,可以与之前的闭包函数相比较。
class Power:def __init__(self, exp):self.exp = expdef __call__(self, base):return base ** self.exps1 = Power(2)square = s1(3) # 这里将实例对象当作函数使用,所以就调用了魔法方法!s2 = Power(3)cube = s2(3)print("平方:", square, "立方:", cube)运行结果:H:\PycharmProjects\pythonProject1\venv\Scripts\python.exe H:\PycharmProjects\pythonProject1\Temporaryfiles.py 平方: 9 立方: 27进程已结束,退出代码0
__str__与__repr__魔法方法
# # 函数讲解# print(str("Python")) # str函数是参数转为为字符串对象# print(repr("Python")) # repr函数是将对象转换为程序可执行的字符串,面向程序# print(eval("1 + 2")) # eval函数可以将参数去引号后执行# print(eval(repr("Python"))) # 可以看出,eval是repr函数的反函数,从结果上可以看出。# 魔法方法""" Note:这两个魔法方法必须是返回字符串的类型,而且__repr__可以对__str__进行代偿 """"""function1"""class C:def __init__(self, data):self.data = datadef __str__(self):return f"data = {self.data}"def __repr__(self):return f"C({self.data})"def __add__(self, other):self.data += otherc = C(250)print(c) # 优先级问题,__str__优先级要比__repr__高,所以会出现打印参数不同的问题运行结果:H:\PycharmProjects\pythonProject1\venv\Scripts\python.exe H:\PycharmProjects\pythonProject1\Temporaryfiles.py data = 250进程已结束,退出代码0"""function2"""class C:def __init__(self, data):self.data = data# def __str__(self):#return f"data = {self.data}"def __repr__(self):return f"C({self.data})"def __add__(self, other):self.data += otherc = C(250)print(c) # 优先级问题,__str__优先级要比__repr__高,所以会出现打印参数不同的问题运行结果:H:\PycharmProjects\pythonProject1\venv\Scripts\python.exe H:\PycharmProjects\pythonProject1\Temporaryfiles.py C(250)进程已结束,退出代码0
如果觉得《Python-面向对象-魔法方法(未完待续)》对你有帮助,请点赞、收藏,并留下你的观点哦!