目录
(1)__len__ ,__getitem__, __setitem__, __delItem__
一、属性相关的魔法方法
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 = name
self.sex = sex
self.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 = name
self.sex = sex
self.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 = name
self.sex = sex
self.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 = name
self.sex = sex
self.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 = name
self.sex = sex
self.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 = name
self.sex = sex
self.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 = name
self.sex = sex
self.age = age
self.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 = name
self.sex = sex
self.age = age
self.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.name
print("删除后:", ls.__dict__)
print("只要没动我老婆就好")
del ls.wife
print("删除后:", 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
二,容器魔法方法
(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}
1
Traceback (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] = value
def __getitem__(self, item):
print("getitem is working")
return self.Dict[item]
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)
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 working
1
getitem is working
1
__setitem__ is working
{'one': 1, 'two': 2}
getitem is working
2
__len__ is working
2
__delitem__ is working
{'one': 1}
__len__ is working
1
进程已结束,退出代码0
只要实例对象发生获取操作就会触发__getitem__ ,从而被__getitem__拦截,其他函数同理!
(2) __iter__, __next__
在实例对象进行迭代操作时触发的魔法方法,__iter__的作用时是将实例对象转变为一个迭代对象,不然的话不能进行__next__操作,__next__就相当于while循环了,注意一定要终止循环,不然被会一直循环下去
time = 0
class Double:
def __init__(self, start, stop):
self.value = start - 1
self.stop = stop
def __iter__(self):
return self
def __next__(self):
global time
if self.value == self.stop:
raise StopIteration
self.value += 1
time += 1
return self.value * 2
d = 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
# 优先级:1
def __contains__(self, item): # 这里的item就是下面的 3 和6
print(item)
print("__contains__ is working")
return item in self.data
# 优先级:2
def __iter__(self):
# print("__iter__ is working")
print("开始", end="->")
self.i = 0
return self
def __next__(self):
# print("__next__ is working")
print("正在查找", end="->")
if self.i == len(self.data):
raise StopIteration
item = self.data[self.i]
self.i += 1
return item # 因为这里返回的是一个非零的参数,所以print的结果就是True
# 优先级:3
def __getitem__(self, item):
print("正在查找", end="->")
return self.data[item] # 因为这里返回的是一个非零的参数,所以print的结果就是True
c = 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 working
True
6
__contains__ is working
False
进程已结束,退出代码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:
# 优先级:1
def __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([])) # 这里由于是空集,所以返回就是False
a = D()
print(bool(a))
print(bool([]))
运行结果:
H:\PycharmProjects\pythonProject1\venv\Scripts\python.exe H:\PycharmProjects\pythonProject1\Temporaryfiles.py
__bool__ is working
True
False
进程已结束,退出代码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
False
False
True
True
进程已结束,退出代码0
__call__魔法方法就相当于将实例对象看作为一个函数,在进行这个操作时就会调用这个魔法方法,可以与之前的闭包函数相比较。
class Power:
def __init__(self, exp):
self.exp = exp
def __call__(self, base):
return base ** self.exp
s1 = 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 = data
def __str__(self):
return f"data = {self.data}"
def __repr__(self):
return f"C({self.data})"
def __add__(self, other):
self.data += other
c = 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 += other
c = C(250)
print(c) # 优先级问题,__str__优先级要比__repr__高,所以会出现打印参数不同的问题
运行结果:
H:\PycharmProjects\pythonProject1\venv\Scripts\python.exe H:\PycharmProjects\pythonProject1\Temporaryfiles.py
C(250)
进程已结束,退出代码0