当前位置:首页>python>【Python】29-魔术方法

【Python】29-魔术方法

  • 2026-09-06 01:32:19
【Python】29-魔术方法

一、魔术方法的定义

定义:以双下划线__开头和结尾的特殊方法,如:__init__、__str__等。
作用:魔术方法就是重写 Python 内置运算符、内置函数的底层逻辑,让你的自定义对象像列表、数字、字符串一样正常使用 +、len ()、print、with、()、[] 等语法。
二、对象生命周期
2.1 __new__和__init__方法
__new__方法:在__init__之前调用,用于创建对象实例。必须返回一个类的实例。即使没有__init__,__new__也必须存在。
__init__方法:在__new__之后调用,用于初始化对象。不返回任何值,只负责设置初始状态。如果__new__返回的是子类实例,则__init__会调用子类的__init__方法。
class A:    def __new__(cls):        print("__new__ 创建空对象")        obj = super().__new__(cls)        return obj    def __init__(self,x):        print("__init__ 初始化属性")        self.x = xo = A(1)
输出:
__new__ 创建空对象__init__ 初始化属性
执行顺序等价于下面的代码:
obj = A.__new__(A)  # 只造对象A.__init__(obj,1)   # 手动传对象+参数,等价自动调用逻辑
2.2 __del__方法

__del__ 析构方法:对象销毁时自动触发,不靠del关键字触发,靠引用计数归0来触发。也就是对象引用计数变为 0 时,Python 自动调用,销毁对象。只是删除变量名和对象的绑定(引用计数 - 1),不是直接销毁对象,只要还有别的变量指向这个对象,引用计数>0,对象存活,__del__不执行。

class A:    def __del__(self):        print("__del__")o = A()   # 实例化对象,引用计数=1x = o     # x也指向同一个对象,引用计数+1 → 引用计数=2del o # 删除变量o,引用计数-1,引用计数=1(对象还在,不会执行__del__)# 代码运行结束、程序退出前,x自动销毁,引用计数变成0 → 触发__del__print("finish")

输出:

finish__del__

三、类型表示与转换

3.1 __repr__方法

控制直接打印对象时显示什么文字。

举例子:

情况1:不写__repr__方法时

class Dog:    def __init__(self,name):        self.name = named = Dog("旺财")print(d) #没写__repr__,输出:<__main__.Dog object at 0xxxxx>

加上 repr 之后

class Dog:    def __init__(self,name):        self.name = name    # __repr__:自定义打印长啥样    def __repr__(self):        return f"小狗:{self.name}"d = Dog("旺财")print(d) # 小狗:旺财

该方法的触发方式:

print(d)         # 调用__repr__print(repr(d))   # 强制调用__repr__

3.2 __str__方法

__str__:给普通人看,print (对象) 优先调用,返回字符串。

__repr__:给程序员看,控制台直接输入对象、列表里打印用,返回字符串。

class Cat:    def __init__(self,name):        self.name = name    def __str__(self):        # 普通人易懂的文字        return f"猫咪名字:{self.name}"c = Cat("小白")print(c)      # print → 自动调用 __str__,输出:猫咪名字:小白

同时写 __str__ 和 __repr__:

class Cat:    def __init__(self,name):        self.name = name    def __str__(self):        return f"猫咪:{self.name}"    def __repr__(self):        return f"Cat('{self.name}')"c = Cat("小黑")print(c)       # 走__str__ → 猫咪:小黑print(repr(c)) # 走__repr__ → Cat('小黑')print([c])     # 列表里打印,走__repr__

也就是:有__str__就优先用__str__做 print。没有__str__,print 会自动去找__repr__。

3.3 __bytes__方法

返回对象的字节串表示。用于将对象序列化为字节流。

执行 bytes(对象) 的时候,自动调用 __bytes__,就必须返回 b'xxx' 字节类型。

不写__bytes__,直接 bytes () 报错。

class User:    def __init__(self,name):        self.name = nameu = User("小明")# bytes(u) 直接报错!没有__bytes__方法

加上 bytes

class User:    def __init__(self,name):        self.name = name    # bytes(对象)自动调用这个方法    def __bytes__(self):        # 字符串转字节:utf-8编码        return self.name.encode("utf-8")u = User("小明")b = bytes(u)   # 触发__bytes__print(b) # b'\xe5\xb0\x8f\xe6\x98\x8e'# 字节变回文字s = b.decode("utf-8")print(s) # 小明

3.4 __format__方法

用 f"{对象:自定义内容}" / format(对象, '自定义内容') 时,自动跑 __format__。:后面写啥,就把啥传给 __format__。

class Fruit:    def __init__(self,name):        self.name = name    # format_spec 就是冒号后面的文字    def __format__(self, format_spec):        # 如果冒号后写big,名字变大写        if format_spec == "big":            return self.name.upper()        # 没写格式就原样返回        return self.nameapple = Fruit("apple")# 不写:xxxprint(f"{apple}") # apple# 写:big → format_spec="big"print(f"{apple:big}") # APPLE

解读:f"{apple:big}"

①、apple → 对象

②、:big → big 传给 __format__ 的第二个参数

③、代码判断是 big → 转大写返回

3.5 __hash__方法

返回对象的哈希值,用于快速比较对象。如果__eq__方法被重写,通常也

需要重写__hash__方法。只写__eq__,不写__hash__,对象不能放进集合、不能当字典键__hash__等于对象的身份证号。

hash(对象) → 自动运行 __hash__,返回一个整数。集合set、字典key靠这个编号存数据。

class Book:    def __init__(self,name):        self.name = name    def __hash__(self):        # 给对象一个编号        return 123b = Book("红楼梦")# 调用hash(b)就去找__hash__拿数字。print(hash(b)) # 输出123

为什么需要 hash?

集合{}不能存重复元素,靠两点:

①、先比哈希值(__hash__),数字不一样 = 不是同一个东西;

②、哈希一样,再用__eq__判断内容是否相等。

class Book:    def __init__(self,name):        self.name = name    # 同名书哈希一样    def __hash__(self):        return hash(self.name)    # 名字相同就是同一本书    def __eq__(self,other):        return self.name == other.nameb1 = Book("西游记")b2 = Book("西游记")s = {b1,b2}print(len(s)) # 结果1,自动去重

记住:

①、相等的对象,哈希必须相同;

②、只写__eq__不写__hash__,对象不能放进集合、不能当字典键。

3.6 __bool__方法

if 判断 /while/bool (对象),自动执行 __bool__,必须返回 True 或 False。

没写__bool__默认规则:没写这个方法,Python 看对象里面有没有数据:有内容 = True,空 = False。

class Person:    def __init__(self, age):        self.age = age    def __bool__(self):        # 年龄大于18返回True,否则False        return self.age >= 18p1 = Person(20)p2 = Person(15)print(bool(p1)) # Trueprint(bool(p2)) # False# if自动调用__bool__if p1:    print("成年")if p2:    print("未成年")#输出:TrueFalse成年

四、运算符重载

4.1 加法

+:__add__

正常:3+5 数字相加。

自己造的类默认不能 对象+数字,写__add__就能让加号生效。加号左边是你的对象,就执行__add__。

class Money:    def __init__(self,money):        self.m = money    # +左边是当前对象,右边是other    def __add__(self,other):        return self.m + otherm = Money(100)res = m + 200   # 自动调用 m.__add__(200)print(res) # 300

下面的运算魔术方法规律同上。

运算符
魔术方法
运算含义
+__add__
加法
-__sub__
减法
*__mul__
乘法
/__truediv__
浮点数除法
//__floordiv__
地板除 (向下取整)
%__mod__
取余 / 取模
**__pow__
幂运算(次方)

4.2 比较运算符

运算符
魔术方法
运算含义
<__lt__
判断是否小于
<=__le__
判断是否小于等于
>__gt__
判断是否大于
>=__ge__
判断是否大于等于
==__eq__
判断是否相等
!=__ne__
判断是否不相等

以__lt__方法为例:

对象1 < 对象2 → 对象1.__lt__(对象2)

class Student:    def __init__(self,age):        self.age = age    def __lt__(self,other):        # < 比较:比年龄        return self.age < other.ages1 = Student(15)s2 = Student(18)print(s1 < s2) # True

4.3 位运算符

运算符
魔术方法
运算含义
&__and__
按位与
|__or__
按位或
~

__invert__

按位非

以__invert__方法为例:

class Num:    def __init__(self, x):        self.x = x    def __invert__(self):        # ~对象 自动执行这里        return ~self.xn = Num(10)res = ~nprint(res) # -11

4.4 成员检查运算符

class MyList:    def __init__(self):        # 内部用列表存数据        self.data = [11,22,33,44]    # 1.__len__ → len(实例)    def __len__(self):        return len(self.data)    # 2.__getitem__ → 对象[index] 取值    def __getitem__(self,index):        return self.data[index]    # 3.__setitem__ → 对象[index]=值 修改    def __setitem__(self,index,value):        self.data[index] = value    # 4.__delitem__ → del 对象[index] 删除    def __delitem__(self,index):        del self.data[index]    # 5.__iter__ + __next__ 实现for遍历    def __iter__(self):        # 返回迭代器        return iter(self.data)# 创建对象lst = MyList()# 1、len(对象)print(len(lst)) # 调用__len__ → 输出4# 2、getitem:lst[下标]print(lst[1]) # 调用__getitem__ → 22print(lst[1:3]) # 还支持切片 [22,33]# 3、setitem:lst[下标]=新值lst[0] = 99 # 调用__setitem__print(lst[0]) # 99# 4、delitem:del lst[下标]del lst[2] # 删除下标2,调用__delitem__print(len(lst)) # 现在只剩3个元素
__iter__方法:
只要执行 iter(对象) 或者 for x in 对象,自动执行 __iter__,并且必须返回迭代器。
案例1:__iter__返回列表迭代器,不用写__next__
class A:    def __iter__(self):        # 返回列表迭代器        return iter([1,2,3])a = A()for i in a:    print(i)#输出:# 1# 2# 3
案例2:__iter__ + __next__
class B:    def __init__(self):        self.x = 1    def __iter__(self):        return self    def __next__(self):        if self.x > 3:            raise StopIteration        res = self.x        self.x += 1        return resb = B()for i in b:    print(i) # 1,2,3

小结:

(1)__iter__返回别人的迭代器,不用写__next__,案例中别人的迭代器指列表[1,2,3];

(2)__iter__返回自己,必须手写__next__控制什么时候停止;

(3)__iter__必须返回迭代器,有它才能被 for 循环。

五、容器协议

5.1 __len__(self)

调用写法
方法
作用
len(对象)__len__(self)
实现序列长度查询
class Shop:    def __init__(self,goods):        self.goods = goods    def __len__(self):        # len()自动调用,必须返回整数        return len(self.goods)s = Shop(["苹果","香蕉","橘子"])print(len(s)) # 3

解读:

(1)len(实例) → 自动执行 __len__;

(2)必须 return 整数;

(3)没写__bool__时,if 判断会借用__len__:长度 0→False,否则 True。

六、属性访问与描述符

6.1 四种普通属性方法

调用形式
魔术方法
核心功能
触发时机
obj.属性名__getattribute__(self, name)
所有属性查询的唯一入口,优先执行
只要通过 obj.xxx 读取属性,无论属性是否存在,必先触发
obj.属性名__getattr__(self, name)
只有属性不存在才执行
当 __getattribute__ 没找到对应属性时,自动触发
obj.属性名 = 新值__setattr__(self, name, value)
拦截所有属性赋值操作
只要通过 obj.xxx = xxx 给属性赋值,无论属性是否存在,都会触发
del obj.属性名__delattr__(self, name)
删除时触发
只要通过 del obj.xxx 删除属性,就会触发
getattr:只有属性不存在才执行。
class Person:    def __getattr__(self,name):        print(f"{name}这个属性不存在")p=Person()print(p.aaa) # aaa这个属性不存在

setattr:只要对象.属性=值 就执行

class Person:    def __setattr__(self,name,val):        print(f"正在给{name}赋值{val}")        self.__dict__[name] = valp = Person()p.name = "小明"

delattr:del 对象.属性 删除时触发

class Person:    def __delattr__(self,name):        print(f"删除属性:{name}")p=Person()p.age=18del p.age
6.2 三种描述方法
调用形式
魔术方法
核心功能
触发时机
obj.目标属性__get__(self, instance, owner)
拦截目标属性的读取操作
当读取宿主类中绑定了描述符的属性时触发
obj.目标属性 = 新值__set__(self, instance, value)
拦截目标属性的赋值操作,可做校验限制
当给宿主类中绑定了描述符的属性赋值时触发
del obj.目标属性__delete__(self, instance)
拦截目标属性的删除操作
当删除宿主类中绑定了描述符的属性时触发
# 1.描述符类:专门看管 priceclass PriceDesc:    # 取值 g.price    def __get__(self, instance, owner):        # instance = g,实际取值从 g._money 拿        return instance._money    # 赋值 g.price=100    def __set__(self, instance, value):        if value < 0:            raise ValueError("价格不能负数")        # 真实数据存在实例的 _money 里        instance._money = value    # 删除 del g.price    def __delete__(self, instance):        print("删除价格")        del instance._money# 2.宿主类class Goods:    # price交给描述符看管    price = PriceDesc()# 创建对象g = Goods()# 赋值:触发__set__,数据存到 g._moneyg.price = 88# 取值:触发__get__,从 g._money 取出print(g.price)# 删除:触发__delete__,删掉g._moneydel g.price
解读:写 g.price,并不是直接往对象存 price,会自动跳进描述符的__set__方法。
七、其他魔术方法
7.1 __call__方法
定义 __call__ 后:实例对象()就能直接调用,把对象变函数。对象()就能自动执行__call__方法。
场景1:计数
class Counter:    def __init__(self):        self.count = 0    def __call__(self):        self.count += 1        print("次数:", self.count)c = Counter()c() #1c() #2c() #3
小结:不用每次调用 c.add(),直接 c() 更简洁。

场景 2:类实现装饰器(高级用法)

class MyLog:    def __call__(self, fn):        def inner():            print('执行前')            fn()            print('执行后')        return inner# 创建对象log = MyLog()# 普通函数def say():    print('hello')# 手动装饰:把函数传给对象say = log(say)# 调用say()
输出:
执行前 hello 执行后

log(say)能运行,全靠类里写了__call__。

换成 @语法(和上面一模一样)

class MyLog:    def __call__(self, fn):        def inner():            print('执行前')            fn()            print('执行后')        return innerlog = MyLog()# @log 等价于 say = log(say)@logdef say():    print('hello')say()

解读:

(1)@log等价于say = log(say);

(2)log(函数),触发__call__方法;

(3)__call__包装函数,返回新 inner;

(4)say()实际跑 inner 里的代码。

场景3:带参数工具类

class Add:    def __call__(self,x,y):        return x+yadd = Add()print(add(3,7)) #10

7.2 __enter__和__exit__

with 对象 as 变量:    代码块

进入with自动执行__enter__,离开with(不管报错与否)自动执行__exit__。

用途:自动打开资源、自动关闭资源(文件、数据库、网络连接)。

class FileDemo:    # with开头,先执行    def __enter__(self):        print("打开资源")        return "我是as后面的变量"    # with代码结束/报错 都会执行    def __exit__(self, exc_type, exc_val, exc_tb):        print("关闭资源")with FileDemo() as f:    print("中间干活")    print(f)

输出:

打开资源中间干活我是as后面的变量关闭资源

解读:

①、FileDemo() 创建对象;

②、进入with → 调用__enter__,返回值赋值给 f;

③、运行 with 里面的代码;

④、代码走完 / 代码报错 → 必定执行__exit__,用来释放资源。

最新文章

随机文章