第27课:Python|魔术方法大全【__init__/__str__/__repr__等常用魔术方法实战】

在这里插入图片描述

文章目录


📖 开篇导读

在前面的课程中,我们已经学习了类的基本定义、属性、方法、封装、继承、多态等面向对象的核心知识。但Python的类还有一些特殊的方法,它们的名称前后各有两个下划线(如__init____str____len__),被称为魔术方法(Magic Methods)或双下方法(Dunder Methods,即Double Underscore的缩写)。

这些魔术方法让你能够定义类的行为,使其与Python的内置功能无缝集成。例如:通过__str__控制print(obj)的输出;通过__len__让自定义容器支持len(obj);通过__add__让对象支持+运算符;通过__getitem__让对象支持索引访问……掌握这些方法,你就能写出“Pythonic”的类,像内置类型一样优雅。

💡 工作场景:在真实开发中,几乎每个类都会用到至少一两个魔术方法:__init__构造对象,__str__提供可读的打印信息,__repr__提供调试用的准确表示,__enter____exit__实现上下文管理器(with语句),__call__让对象变成可调用的函数等。

本课将系统讲解最常用的魔术方法,通过大量案例让你理解它们的含义和应用场景。学完本课,你将能够设计出与Python语言完美融合的类。


🎯 学习目标

目标编号具体掌握内容对应面试/工作价值
1️⃣掌握对象表示相关魔术方法:__str____repr__的区别及应用让打印输出更友好
2️⃣掌握容器模拟方法:__len____getitem____setitem____delitem__实现自定义序列/映射
3️⃣掌握运算符重载:__add____eq____lt__让类支持数学运算和比较
4️⃣掌握可调用对象:__call__方法创建带状态的函数
5️⃣掌握上下文管理器:__enter____exit__支持with语句
6️⃣掌握属性访问控制:__getattr____setattr__动态属性、代理模式

🔥 面试考点:“__str____repr__的区别?”“如何让自定义类支持len()和索引?”“__call__的作用?”“什么是上下文管理器?如何实现?”


📚 知识点理论精讲

一、对象表示:__str____repr__

1.1 基本概念

  • __str__:面向用户的可读性表示,供print()str()调用。
  • __repr__:面向开发者的准确表示,通常应该是可用来重新创建该对象的字符串,供交互式环境和repr()调用。如果没有定义__str__,Python会使用__repr__作为后备。
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def __str__(self):
        return f"Person(name={self.name}, age={self.age})"
    
    def __repr__(self):
        return f"Person('{self.name}', {self.age})"

p = Person("张三", 25)
print(p)          # 调用 __str__
print(repr(p))    # 调用 __repr__

最佳实践

  • __repr__尽量返回能够重新创建对象的表达式字符串。
  • __str__返回简洁友好的描述。

💡 工作应用:调试时,交互式环境默认使用__repr__;日志记录中通常使用__str__


二、容器模拟:__len____getitem____setitem____delitem__

实现这些方法可以让自定义类表现得像列表或字典。

2.1 __len____getitem__ 实现序列协议

class MyList:
    def __init__(self, items):
        self._items = list(items)
    
    def __len__(self):
        return len(self._items)
    
    def __getitem__(self, index):
        return self._items[index]

ml = MyList([1,2,3])
print(len(ml))   # 3
print(ml[1])     # 2
for item in ml:  # 可迭代
    print(item)

2.2 __setitem____delitem__ 支持修改和删除

class MutableList(MyList):
    def __setitem__(self, index, value):
        self._items[index] = value
    
    def __delitem__(self, index):
        del self._items[index]

ml = MutableList([1,2,3])
ml[1] = 99
del ml[0]
print(list(ml))  # [99,3]

2.3 实现映射(类似字典)

class DictLike:
    def __init__(self):
        self._data = {}
    
    def __getitem__(self, key):
        return self._data[key]
    
    def __setitem__(self, key, value):
        self._data[key] = value
    
    def __delitem__(self, key):
        del self._data[key]
    
    def __len__(self):
        return len(self._data)

d = DictLike()
d["name"] = "张三"
print(d["name"])  # 张三
print(len(d))     # 1

三、运算符重载:__add____sub____eq__

通过魔术方法,可以让自定义对象支持算术运算和比较。

3.1 算术运算符

运算符魔术方法
+__add__
-__sub__
*__mul__
/__truediv__
//__floordiv__
%__mod__
**__pow__

3.2 比较运算符

运算符魔术方法
==__eq__
!=__ne__
<__lt__
<=__le__
>__gt__
>=__ge__

3.3 示例:二维向量

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)
    
    def __sub__(self, other):
        return Vector(self.x - other.x, self.y - other.y)
    
    def __eq__(self, other):
        return self.x == other.x and self.y == other.y
    
    def __repr__(self):
        return f"Vector({self.x}, {self.y})"

v1 = Vector(1,2)
v2 = Vector(3,4)
v3 = v1 + v2
print(v3)   # Vector(4,6)
print(v1 == Vector(1,2))  # True

四、可调用对象:__call__

如果一个类定义了__call__方法,其实例就可以像函数一样被调用。

class Multiplier:
    def __init__(self, factor):
        self.factor = factor
    
    def __call__(self, x):
        return x * self.factor

double = Multiplier(2)
print(double(5))   # 10
print(callable(double))  # True

应用场景:实现带状态的函数,替代闭包。


五、上下文管理器:__enter____exit__

用于实现with语句,自动管理资源(如文件、锁、数据库连接)。

class ManagedFile:
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode
    
    def __enter__(self):
        self.file = open(self.filename, self.mode)
        return self.file
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.file.close()
        # 如果返回 True,异常会被抑制;返回 False 则向上传播
        return False

with ManagedFile("test.txt", "w") as f:
    f.write("Hello")

六、属性访问控制:__getattr____setattr____delattr__

  • __getattr__(self, name):当通过正常方式找不到属性时调用。
  • __getattribute__(self, name):每次访问属性都会调用(慎用,易递归)。
  • __setattr__(self, name, value):每次设置属性时调用。
  • __delattr__(self, name):删除属性时调用。

6.1 __getattr__ 实现动态属性

class DynamicPerson:
    def __init__(self, name):
        self.name = name
    
    def __getattr__(self, name):
        return f"{name} is not defined"

p = DynamicPerson("张三")
print(p.age)   # age is not defined

6.2 __setattr__ 实现属性验证

class Positive:
    def __init__(self, value):
        self.value = value   # 会调用 __setattr__
    
    def __setattr__(self, name, value):
        if name == "value" and value < 0:
            raise ValueError("value must be positive")
        super().__setattr__(name, value)  # 避免递归

七、其他常用魔术方法

魔术方法作用
__new__(cls, ...)控制对象创建,返回实例
__del__(self)对象被销毁前调用(析构)
__hash__(self)定义哈希值,用于字典键和集合
__bool__(self)定义布尔测试,if obj调用
__iter__(self)返回迭代器,用于for循环
__next__(self)迭代器迭代时返回下一个值
__contains__(self, item)支持in运算符

💻 代码案例实操

案例1:__str____repr__ 对比

"""
str_repr_demo.py
演示__str__和__repr__的区别
"""

class Book:
    def __init__(self, title, author, pages):
        self.title = title
        self.author = author
        self.pages = pages
    
    def __str__(self):
        """用户友好的显示"""
        return f"《{self.title}》 by {self.author}"
    
    def __repr__(self):
        """开发者友好的显示,可用于重建对象"""
        return f"Book('{self.title}', '{self.author}', {self.pages})"

book = Book("Python编程", "张三", 350)
print(str(book))    # 《Python编程》 by 张三
print(repr(book))   # Book('Python编程', '张三', 350)

# 列表中的对象会使用 __repr__
print([book])       # [Book('Python编程', '张三', 350)]

案例2:自定义序列(可迭代、支持切片)

"""
custom_sequence.py
实现一个支持切片和迭代的自定义序列
"""

class MyRange:
    """模拟内置range,但支持更多功能"""
    def __init__(self, start, stop=None, step=1):
        if stop is None:
            start, stop = 0, start
        self.start = start
        self.stop = stop
        self.step = step
    
    def __len__(self):
        """计算元素个数"""
        if self.step > 0:
            if self.start >= self.stop:
                return 0
            return (self.stop - self.start - 1) // self.step + 1
        else:
            if self.start <= self.stop:
                return 0
            return (self.start - self.stop - 1) // (-self.step) + 1
    
    def __getitem__(self, index):
        """支持索引和切片"""
        if isinstance(index, slice):
            # 处理切片
            start, stop, step = index.indices(len(self))
            return [self[i] for i in range(start, stop, step)]
        else:
            # 处理整数索引
            if index < 0:
                index += len(self)
            if index < 0 or index >= len(self):
                raise IndexError("index out of range")
            return self.start + index * self.step
    
    def __iter__(self):
        """支持迭代"""
        current = self.start
        while (self.step > 0 and current < self.stop) or (self.step < 0 and current > self.stop):
            yield current
            current += self.step
    
    def __repr__(self):
        return f"MyRange({self.start}, {self.stop}, {self.step})"

# 测试
r = MyRange(1, 10, 2)
print(len(r))        # 5
print(list(r))       # [1,3,5,7,9]
print(r[2])          # 5
print(r[1:4])        # [3,5,7]

案例3:二维向量(运算符重载)

"""
vector_ops.py
演示算术和比较运算符重载
"""

class Vector2D:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __add__(self, other):
        return Vector2D(self.x + other.x, self.y + other.y)
    
    def __sub__(self, other):
        return Vector2D(self.x - other.x, self.y - other.y)
    
    def __mul__(self, scalar):
        """数乘,注意标量应在右侧"""
        if isinstance(scalar, (int, float)):
            return Vector2D(self.x * scalar, self.y * scalar)
        return NotImplemented
    
    def __rmul__(self, scalar):
        """左乘法:scalar * vector"""
        return self.__mul__(scalar)
    
    def __eq__(self, other):
        return self.x == other.x and self.y == other.y
    
    def __abs__(self):
        return (self.x**2 + self.y**2) ** 0.5
    
    def __repr__(self):
        return f"Vector2D({self.x}, {self.y})"

v1 = Vector2D(1,2)
v2 = Vector2D(3,4)
v3 = v1 + v2
print(v3)                # Vector2D(4,6)
v4 = 2 * v1
print(v4)                # Vector2D(2,4)
print(abs(v1))           # 2.236...
print(v1 == Vector2D(1,2))  # True

案例4:可调用对象实现累加器

"""
callable_accumulator.py
使用__call__实现带状态的累加器
"""

class Accumulator:
    def __init__(self, start=0):
        self.value = start
    
    def __call__(self, increment=1):
        self.value += increment
        return self.value
    
    def reset(self):
        self.value = 0

acc = Accumulator(10)
print(acc())        # 11
print(acc(5))       # 16
print(acc())        # 17
acc.reset()
print(acc())        # 1

# 作为装饰器使用
class CountCalls:
    def __init__(self, func):
        self.func = func
        self.count = 0
    
    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"{self.func.__name__} 被调用第 {self.count} 次")
        return self.func(*args, **kwargs)

@CountCalls
def say_hello():
    print("Hello")

say_hello()
say_hello()

案例5:上下文管理器实现计时器

"""
context_timer.py
使用__enter__和__exit__实现计时上下文管理器
"""

import time

class Timer:
    def __init__(self, name="Block"):
        self.name = name
    
    def __enter__(self):
        self.start = time.perf_counter()
        return self  # 可以返回对象供with内部使用
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.end = time.perf_counter()
        elapsed = self.end - self.start
        print(f"{self.name} 耗时: {elapsed:.4f}秒")
        # 如果发生异常,不抑制(返回False)
        return False
    
    def elapsed(self):
        return self.end - self.start

# 使用
with Timer("循环1") as t:
    time.sleep(0.5)

# 嵌套
with Timer("外层"):
    time.sleep(0.2)
    with Timer("内层"):
        time.sleep(0.3)

案例6:动态属性代理(__getattr____setattr__

"""
dynamic_proxy.py
实现一个代理类,动态转发属性
"""

class Proxy:
    def __init__(self, obj):
        self._obj = obj
    
    def __getattr__(self, name):
        print(f"获取属性: {name}")
        return getattr(self._obj, name)
    
    def __setattr__(self, name, value):
        if name == '_obj':
            super().__setattr__(name, value)
        else:
            print(f"设置属性: {name} = {value}")
            setattr(self._obj, name, value)
    
    def __delattr__(self, name):
        print(f"删除属性: {name}")
        delattr(self._obj, name)

# 使用
class Data:
    def __init__(self):
        self.value = 100
        self.name = "data"

d = Data()
p = Proxy(d)
print(p.value)   # 获取属性: value -> 100
p.name = "new"   # 设置属性: name = new
print(d.name)    # new
del p.name       # 删除属性: name

案例7:实现一个类似字典但带默认值的类(综合)

"""
default_dict_like.py
模拟collections.defaultdict,使用多个魔术方法
"""

class DefaultDict:
    def __init__(self, default_factory=None):
        self._data = {}
        self.default_factory = default_factory
    
    def __getitem__(self, key):
        if key in self._data:
            return self._data[key]
        elif self.default_factory is not None:
            value = self.default_factory()
            self._data[key] = value
            return value
        else:
            raise KeyError(key)
    
    def __setitem__(self, key, value):
        self._data[key] = value
    
    def __delitem__(self, key):
        del self._data[key]
    
    def __contains__(self, key):
        return key in self._data
    
    def __len__(self):
        return len(self._data)
    
    def __iter__(self):
        return iter(self._data)
    
    def __repr__(self):
        return f"DefaultDict({self._data})"
    
    def get(self, key, default=None):
        try:
            return self[key]
        except KeyError:
            return default

# 使用
d = DefaultDict(list)
d["fruits"].append("apple")
d["fruits"].append("banana")
d["numbers"].append(1)
print(d["fruits"])   # ['apple', 'banana']
print(d["unknown"])  # [](自动创建空列表)
print(len(d))        # 3
print("fruits" in d) # True
for key in d:
    print(key, d[key])

⚠️ 易错点避坑总结

序号坑点描述后果解决方案
1__getattr____getattribute__混淆__getattribute__每次访问都调用,易导致无限递归一般重写__getattr__即可;若需__getattribute__,注意调用super()
2__setattr__中直接赋值self.name = value无限递归,栈溢出使用super().__setattr__(name, value)
3忘记实现__iter____getitem__导致对象不迭代for循环无效至少实现__getitem____iter__
4运算符重载中返回NotImplemented而不是None可能导致运算失败当不支持操作数类型时返回NotImplemented
5上下文管理器的__exit__中忽略异常参数异常被抑制或无法处理根据exc_type决定是否处理,返回True抑制
6__repr__不够准确,无法重建对象调试困难尽量返回ClassName(...)格式
7__hash____eq__同时定义时,若对象可变则不应定义__hash__放入字典后修改属性导致无法找到可变对象不定义__hash__(不可哈希)
8__new__中忘记返回实例__init__不被调用,返回None总是返回super().__new__(cls)
9__del__中引用全局变量可能已被销毁异常避免在__del__中依赖全局变量
10切片操作只处理整数索引,未处理slice对象不支持切片检查isinstance(index, slice)

📝 课后实战练习题

第1题:__str____repr__

定义一个Money类,属性amount(浮点数)、currency(货币单位,如"CNY")。实现__str__返回格式"<货币符号>金额"(如"¥100.00"),__repr__返回"Money(amount, currency)"。创建实例测试。

第2题:实现不可变二维点

定义Point类,属性xy为只读(通过@property),实现__add____sub____eq____repr__。创建点进行加减和比较。

第3题:可调用类实现斐波那契数列

定义Fibonacci类,实例可调用,每次调用返回下一个斐波那契数。例如fib = Fibonacci(); print(fib()); print(fib()) 输出0,1,1,2,…。支持重置。

第4题:实现上下文管理器@timed

实现一个装饰器@timed,它使用Timer上下文管理器,在函数执行前后打印执行时间。要求不修改原函数。

第5题:自定义序列ReversedList

实现ReversedList类,接受一个可迭代对象,在内部存储正向列表。但索引访问和迭代时返回逆向顺序。实现__len____getitem____iter__

第6题:属性代理验证

定义Validated类,在__setattr__中验证属性名不能以_开头,且属性值不能为None。提示:使用super().__setattr__

第7题:模拟defaultdict(已做案例,扩展)

在案例7的基础上,增加keys()values()items()方法,返回视图或列表。并支持update方法。


🧠 知识点思维导图总结

第27课:魔术方法

对象表示

str 可读性

repr 准确性

容器模拟

len 长度

getitem 索引/切片

setitem 赋值

delitem 删除

contains in运算符

运算符重载

add 加法

sub 减法

mul 乘法

eq 等于

lt 小于

可调用对象

call

带状态的函数

上下文管理器

enter

exit

with语句

属性访问

getattr 缺省属性

setattr 属性设置

delattr 删除

其他常用

new 创建

del 销毁

hash 哈希

bool 布尔

iter/next 迭代器

面试考点

str vs repr

实现迭代器

上下文管理器

运算符重载


🔜 下节课预告

魔术方法让你能够控制类的行为,使其更加Pythonic。下一节课我们将学习设计模式入门,了解单例模式、工厂模式等常见设计模式及其在Python中的实现。

第28课:设计模式入门:单例模式、工厂模式Python极简实现

内容包括:

  • 设计模式的概念和分类
  • 单例模式(Singleton)的多种实现方式
  • 工厂模式(简单工厂、工厂方法、抽象工厂)
  • 实战:数据库连接池、日志器、图形工厂

设计模式是面向对象设计的经验总结,掌握它们能让你的代码更加优雅、可维护。

🌟 学习鼓励:魔术方法是Python语言的精髓之一。掌握它们,你就能写出与内置类型无缝衔接的类。不要试图一次记住所有魔术方法,先掌握最常用的__str____repr____len____getitem____call____enter__/__exit__,再根据需要查阅其他方法。动手完成练习,让魔术方法成为你的肌肉记忆。


🔗《50节课 Python 从入门到精通》系列课程导航

去订阅

🌟 感谢您耐心阅读到这里!
💡 如果本文对您有所启发欢迎:
👍 点赞📌 收藏 📤 分享给更多需要的伙伴。
🗣️ 期待在评论区看到您的想法, 共同进步。
🔔 关注我,持续获取更多干货内容~
🤗 我们下篇文章见~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Thomas.Sir

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值