苦练Python第53天:数值运算魔术方法从入门到精通

前言

大家好,我是 倔强青铜三 。欢迎关注我,微信公众号:倔强青铜三。欢迎点赞、收藏、关注,一键三连!

欢迎来到 苦练Python第53天

今天咱们杀进 面向对象 深水区------数值运算魔术方法

当你写下 a + blen(c)abs(d) 这些看似简单的语句时,Python 背后其实偷偷调用了对象内部的 魔术方法

一口气吃透这 20+ 个魔术方法,从此自定义对象也能像内置 intfloatcomplex 一样丝滑参与运算!


1. 为什么需要数值运算魔术方法?

  • 让自定义类的实例支持 + - * / 等运算符
  • 无缝接入内置函数 abs() round() divmod()
  • 提供与内置数值类型一致的交互体验
  • 在科学计算、金融模型、游戏引擎里大放异彩

2. 魔术方法全景图速查表

类别 魔术方法 触发场景 对应表达式/函数
一元运算 __neg__ 负号 -obj
__pos__ 正号 +obj
__abs__ 绝对值 abs(obj)
__invert__ 按位取反 ~obj
__round__ 四舍五入 round(obj[, ndigits])
__floor__ 向下取整 math.floor(obj)
__ceil__ 向上取整 math.ceil(obj)
__trunc__ 截断取整 math.trunc(obj)
二元运算 __add__ 加法 a + b
__radd__ 右加法(反射) 3 + a
__iadd__ 原地加法 a += b
__sub__ 减法 a - b
__rsub__ 右减法 3 - a
__isub__ 原地减法 a -= b
__mul__ 乘法 a * b
__rmul__ 右乘法 3 * a
__imul__ 原地乘法 a *= b
__truediv__ 真除法 a / b
__rtruediv__ 右真除法 3 / a
__itruediv__ 原地真除法 a /= b
__floordiv__ 地板除 a // b
__rfloordiv__ 右地板除 3 // a
__ifloordiv__ 原地地板除 a //= b
__mod__ 取模 a % b
__rmod__ 右取模 3 % a
__imod__ 原地取模 a %= b
__pow__ 幂运算 a ** bpow(a, b[, mod])
__rpow__ 右幂运算 3 ** a
__ipow__ 原地幂运算 a **= b
__divmod__ 同时返回商和余 divmod(a, b)
__rdivmod__ 右divmod divmod(3, a)
__lshift__ 左移 a << b
__rlshift__ 右左移 3 << a
__ilshift__ 原地左移 a <<= b
__rshift__ 右移 a >> b
__rrshift__ 右右移 3 >> a
__irshift__ 原地右移 a >>= b
__and__ 按位与 a & b
__rand__ 右按位与 3 & a
__iand__ 原地按位与 a &= b
__or__ 按位或 `a
__ror__ 右按位或 `3
__ior__ 原地按位或 `a
__xor__ 按位异或 a ^ b
__rxor__ 右按位异或 3 ^ a
__ixor__ 原地按位异或 a ^= b
类型转换 __int__ 转 int int(obj)
__float__ 转 float float(obj)
__complex__ 转 complex complex(obj)
__bool__ 转 bool bool(obj)
__index__ 整数下标 operator.index(obj)、切片

3. 一元运算魔术方法详解

3.1 __neg____pos__

python 复制代码
class Vector2D:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __neg__(self):
        """触发场景:-v"""
        print("__neg__ 被调用!")
        return Vector2D(-self.x, -self.y)

    def __pos__(self):
        """触发场景:+v"""
        print("__pos__ 被调用!")
        return Vector2D(self.x, self.y)  # 通常返回自身或副本

    def __repr__(self):
        return f"Vector2D({self.x}, {self.y})"

# 触发示例
v = Vector2D(3, 4)
print(-v)  # __neg__ 被调用!
print(+v)  # __pos__ 被调用!

3.2 __abs__

python 复制代码
import math

class Vector2D:
    ...
    def __abs__(self):
        """触发场景:abs(v)"""
        print("__abs__ 被调用!")
        return math.hypot(self.x, self.y)

print(abs(v))  # __abs__ 被调用! -> 5.0

3.3 __invert__(按位取反)

python 复制代码
class BitInt:
    def __init__(self, value):
        self.value = int(value)

    def __invert__(self):
        """触发场景:~bit"""
        print("__invert__ 被调用!")
        return BitInt(~self.value)

    def __repr__(self):
        return str(self.value)

b = BitInt(5)
print(~b)  # __invert__ 被调用! -> -6

3.4 取整三兄弟:__round__ / __floor__ / __ceil__ / __trunc__

python 复制代码
import math

class ApproxFloat:
    def __init__(self, value):
        self.value = float(value)

    def __round__(self, ndigits=None):
        """触发场景:round(obj[, nd])"""
        print("__round__ 被调用!")
        return round(self.value, ndigits)

    def __floor__(self):
        """触发场景:math.floor(obj)"""
        print("__floor__ 被调用!")
        return math.floor(self.value)

    def __ceil__(self):
        """触发场景:math.ceil(obj)"""
        print("__ceil__ 被调用!")
        return math.ceil(self.value)

    def __trunc__(self):
        """触发场景:math.trunc(obj)"""
        print("__trunc__ 被调用!")
        return math.trunc(self.value)

af = ApproxFloat(3.14159)
print(round(af, 2))   # __round__ 被调用! -> 3.14
print(math.floor(af)) # __floor__ 被调用! -> 3
print(math.ceil(af))  # __ceil__ 被调用! -> 4
print(math.trunc(af)) # __trunc__ 被调用! -> 3

4. 二元运算魔术方法详解

下面以 加法 为例,其他运算符模式完全一样,只是名字不同。

4.1 __add____radd__(反射)

python 复制代码
class Dollar:
    def __init__(self, amount):
        self.amount = float(amount)

    def __add__(self, other):
        """
        触发场景:d1 + d2  或  d1 + 10
        other 可能是 Dollar 或 int/float
        """
        print("__add__ 被调用!")
        if isinstance(other, Dollar):
            return Dollar(self.amount + other.amount)
        return Dollar(self.amount + other)

    def __radd__(self, other):
        """
        触发场景:10 + d1
        仅在左操作数未实现加法或返回 NotImplemented 时触发
        """
        print("__radd__ 被调用!")
        return self + other  # 复用 __add__

    def __repr__(self):
        return f"${self.amount:.2f}"

d1 = Dollar(10)
d2 = Dollar(5.5)
print(d1 + d2)  # __add__ 被调用! -> $15.50
print(d1 + 3)   # __add__ 被调用! -> $13.00
print(7 + d1)   # __radd__ 被调用! -> $17.00

4.2 原地运算:__iadd__

python 复制代码
class Dollar:
    ...
    def __iadd__(self, other):
        """触发场景:d1 += d2 或 d1 += 10"""
        print("__iadd__ 被调用!")
        if isinstance(other, Dollar):
            self.amount += other.amount
        else:
            self.amount += other
        return self  # 原地修改必须返回 self

d = Dollar(100)
d += Dollar(25)
print(d)  # __iadd__ 被调用! -> $125.00

4.3 其他二元运算示例(乘法为例)

python 复制代码
class SquareMatrix:
    def __init__(self, n, data):
        self.n = n
        self.data = data  # 一维列表按行展开

    def __matmul__(self, other):
        """矩阵乘法 A @ B"""
        # 这里故意简化,仅演示触发
        print("__matmul__ 被调用!")
        return self  # 略去计算

# 触发示例
A = SquareMatrix(2, [1, 2, 3, 4])
B = SquareMatrix(2, [5, 6, 7, 8])
_ = A @ B  # __matmul__ 被调用!

所有二元运算都具有 __op__ / __rop__ / __iop__ 三种形态,不再逐一举例 。你可以把上面的 __add__/__radd__/__iadd__ 模板直接套用到 __sub____mul____truediv____floordiv____mod____pow____lshift____rshift____and____or____xor__

4.4 __divmod____rdivmod__

python 复制代码
class Minute:
    def __init__(self, total):
        self.total = int(total)

    def __divmod__(self, other):
        """触发场景:divmod(min, 60)"""
        print("__divmod__ 被调用!")
        hours, mins = divmod(self.total, int(other))
        return Minute(hours), Minute(mins)

    def __rdivmod__(self, other):
        """触发场景:divmod(120, min)"""
        print("__rdivmod__ 被调用!")
        return divmod(other, self.total)

    def __repr__(self):
        return f"{self.total}min"

m = Minute(135)
print(divmod(m, 60))  # __divmod__ 被调用! -> (2min, 15min)
print(divmod(120, m)) # __rdivmod__ 被调用! -> (0, 120)

5. 位运算魔术方法(位与为例)

python 复制代码
class BitMask:
    def __init__(self, value):
        self.value = int(value) & 0xFF  # 8bit

    def __and__(self, other):
        """触发场景:bm1 & bm2 或 bm & 0x0F"""
        print("__and__ 被调用!")
        return BitMask(self.value & int(other))

    def __rand__(self, other):
        """触发场景:0x0F & bm"""
        print("__rand__ 被调用!")
        return BitMask(int(other) & self.value)

    def __repr__(self):
        return f"0b{self.value:08b}"

bm1 = BitMask(0b11110000)
bm2 = BitMask(0b10101010)
print(bm1 & bm2)  # __and__ 被调用! -> 0b10100000
print(0b00001111 & bm1)  # __rand__ 被调用! -> 0b00000000

6. 类型转换魔术方法

6.1 __int__ / __float__ / __complex__

python 复制代码
class Fraction:
    def __init__(self, numerator, denominator):
        self.num = numerator
        self.den = denominator

    def __int__(self):
        """触发场景:int(frac)"""
        print("__int__ 被调用!")
        return self.num // self.den

    def __float__(self):
        """触发场景:float(frac)"""
        print("__float__ 被调用!")
        return self.num / self.den

    def __complex__(self):
        """触发场景:complex(frac)"""
        print("__complex__ 被调用!")
        return complex(float(self), 0)

    def __bool__(self):
        """触发场景:bool(frac)"""
        print("__bool__ 被调用!")
        return bool(self.num)  # 0 为 False

    def __index__(self):
        """触发场景:lst[frac]、bin(frac) 等"""
        print("__index__ 被调用!")
        return int(self)

f = Fraction(7, 2)
print(int(f))      # __int__ 被调用! -> 3
print(float(f))    # __float__ 被调用! -> 3.5
print(complex(f))  # __complex__ 被调用! -> (3.5+0j)
print(bool(f))     # __bool__ 被调用! -> True
lst = [10, 20, 30]
print(lst[f])      # __index__ 被调用! -> 30

7. 实战:打造可运算的"秒表时间"类

python 复制代码
from functools import total_ordering

@total_ordering
class Stopwatch:
    """
    支持 + - * / 比较运算、abs、int、float 的时间类
    单位为秒,内部用 float 存储
    """
    def __init__(self, seconds=0.0):
        self.sec = float(seconds)

    # 一元运算
    def __neg__(self):
        return Stopwatch(-self.sec)

    def __abs__(self):
        return Stopwatch(abs(self.sec))

    # 二元运算
    def __add__(self, other):
        if isinstance(other, Stopwatch):
            return Stopwatch(self.sec + other.sec)
        return Stopwatch(self.sec + float(other))

    __radd__ = __add__  # 加法可交换

    def __sub__(self, other):
        if isinstance(other, Stopwatch):
            return Stopwatch(self.sec - other.sec)
        return Stopwatch(self.sec - float(other))

    def __rsub__(self, other):
        return Stopwatch(float(other) - self.sec)

    def __mul__(self, factor):
        return Stopwatch(self.sec * float(factor))

    __rmul__ = __mul__

    def __truediv__(self, divisor):
        return Stopwatch(self.sec / float(divisor))

    # 比较运算
    def __eq__(self, other):
        return self.sec == float(other)

    def __lt__(self, other):
        return self.sec < float(other)

    # 类型转换
    def __int__(self):
        return int(self.sec)

    def __float__(self):
        return self.sec

    def __repr__(self):
        return f"{self.sec:.2f}s"

# 触发示例
t1 = Stopwatch(120)
t2 = Stopwatch(45)
print(t1 + t2)  # 165.00s
print(3 * t1)   # 360.00s
print(abs(-t1)) # 120.00s
print(sorted([t2, t1, Stopwatch(90)]))  # [45.00s, 90.00s, 120.00s]

8. 小结 & 思维导图

  • 一元运算 让对象支持 - + abs ~ round floor ceil trunc
  • 二元运算 通过 __op__ / __rop__ / __iop__ 覆盖全部算术、位运算
  • 类型转换 让对象可以 int() float() complex() bool() 甚至切片
  • 只要 方法返回值类型正确,Python 就能把你的对象当成原生数值一样用!

9. 动手挑战

  1. 实现一个 Vector3D 类,支持向量加、减、点乘、叉乘、取模(abs)和打印。

  2. 写一个 Money 类,支持 + - * / 与浮点数交互,并且每次运算都保留 2 位小数。

  3. 进阶:让 Money 支持原地运算 += -= *= /=,并且链式比较 m1 < m2 <= m3

完成后把代码贴到评论区,互相 Review!

最后感谢阅读!欢迎关注我,微信公众号倔强青铜三

一键三连(点赞、收藏、关注)!

相关推荐
Q_Q5110082853 小时前
python+springboot+uniapp基于微信小程序的停车场管理系统 弹窗提示和车牌识别
vue.js·spring boot·python·django·flask·uni-app·node.js
yaso_zhang3 小时前
jetpack6.1 的新 pytorch 2.5.1 版本在哪里?下载中心仅提供 pytorch v2.5.0a0。
人工智能·pytorch·python
金井PRATHAMA3 小时前
语义三角论对人工智能自然语言处理深层语义分析的影响与启示
人工智能·自然语言处理·知识图谱
Canace3 小时前
我们是否需要AI知识库
人工智能·aigc·ai编程
en-route3 小时前
从零开始学神经网络——前馈神经网络
人工智能·深度学习·神经网络
前端伪大叔3 小时前
第13篇:🎯 如何精准控制买入卖出价格?entry/exit\_pricing 实战配置
javascript·python
城南皮卡丘3 小时前
基于YOLO8+flask+layui的行人跌倒行为检测系统【源码+模型+数据集】
python·flask·layui
weixin_446260854 小时前
解锁安全新维度:Cybersecurity AI (CAI) 助力提升网络安全效率!
人工智能·安全·web安全
成成成成成成果4 小时前
软件测试面试八股文:测试技术 10 大核心考点(二)
python·功能测试·测试工具·面试·职场和发展·安全性测试