Python-面向对象-魔法方法(未完待续)

目录

一、属性相关的魔法方法

(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 = 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

源视频:https://www.bilibili.com/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}
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