重载的推荐使用场景及示例

举报
码乐 发表于 2025/09/06 09:02:41 2025/09/06
【摘要】 1 简介如何运用运用示例,如何“使用”,Go:不能重载,只能“用现成规则”或“用方法/函数替代”可以定义具名类型并沿用其底层类型的运算语义,但不能改变运算符的含义: type MyInt int func f(a, b MyInt) MyInt { return a + b // 可以:语义与 int 完全一致 } // MyInt + int 不行:需要显式类型转换自定...

1 简介如何运用

运用示例,如何“使用”,Go:不能重载,只能“用现成规则”或“用方法/函数替代”

可以定义具名类型并沿用其底层类型的运算语义,但不能改变运算符的含义:

  type MyInt int

  func f(a, b MyInt) MyInt {
      return a + b // 可以:语义与 int 完全一致
  }
  // MyInt + int 不行:需要显式类型转换

自定义结构体不能直接用 + 等,需要写方法或函数:

  type Vec2 struct{ X, Y float64 }

  func (v Vec2) Add(u Vec2) Vec2 { return Vec2{v.X + u.X, v.Y + u.Y} }

  v := Vec2{1, 2}.Add(Vec2{3, 4}) // ✅ 通过方法,而非运算符

泛型(1.18+)能在约束允许的情况下对“数值型”做通用加减乘除,但依旧不是重载:

  type Number interface {
      ~int | ~int64 | ~float64 // ~ 表示底层类型
  }
  func Add[T Number](a, b T) T { return a + b }

要点:Go 的运算符语义对每种类型在语言层面固定,用户代码不能改变。

2 Python 3示例重载

实现数据模型的特殊方法即是“重载”

以向量为例:

    class Vec2:
        def __init__(self, x, y):
            self.x, self.y = x, y

        def __add__(self, other):        # v + u
            if not isinstance(other, Vec2):
                return NotImplemented
            return Vec2(self.x + other.x, self.y + other.y)

        def __radd__(self, other):       # 支持 sum([...], 0) 等右侧回退
            return self.__add__(other)

        def __iadd__(self, other):       # v += u,若不实现会回退到 __add__
            if not isinstance(other, Vec2):
                return NotImplemented
            self.x += other.x; self.y += other.y
            return self

        def __mul__(self, k):            # v * 3
            if not isinstance(k, (int, float)):
                return NotImplemented
            return Vec2(self.x * k, self.y * k)

        def __rmul__(self, k):           # 3 * v
            return self.__mul__(k)

        def __eq__(self, other):         # v == u
            return isinstance(other, Vec2) and self.x == other.x and self.y == other.y

其他可重载的运算符举例:

  算术 __sub__ __truediv__ __floordiv__ __mod__ __pow__;
  位运算 __and__ __or__ __xor__ __lshift__ __rshift__;
  比较 __lt__ __le__ __gt__ __ge__ __ne__;
  下标/切片 __getitem__ __setitem__;
  容器与转换 __len__ __contains__ __iter__ __index__;
  矩阵乘法 __matmul__(@)等。

要点:Python 通过类上的魔术方法把运算符映射到可自定义的行为。

3、内部实现与分派算法的本质差异

Go:编译期静态决定,运行时直接执行固化语义

语义固定:运算符的含义写在语言规范里,编译器在类型检查阶段就决定每个 + - * / == < 等具体含义。

代码生成:

对于数值/布尔运算,编译器直接生成机器指令或调用极少量运行库(如字符串拼接会调runtime.concatstring)。

比较:

基本类型:直接比较。

结构体/数组:编译器生成逐字段/逐元素比较代码(可能调用内置的内存相等例程),前提是字段/元素本身“可比较”。

map/slice/func:不可比较(除与 nil 比较)。

接口值比较:借助类型描述与已知的“相等函数”路径完成。

无动态回退/反向分派:a+b 的语义在编译期锁死,不会因为右操作数类型是子类型而换另一套逻辑。

泛型:即便使用类型形参与约束,运算符仍然是对实参类型的既定语义的静态绑定;编译器通过实例化/字典传参等策略生成代码,但不引入“可重写的运算符行为”。

Go 把“运算符含义”当成语法和类型系统的一部分,而不是用户可扩展的接口。

  • Python 3(以 CPython 为例):运行期经“类型槽”与魔术方法动态分派

操作码触发:字节码如 BINARY_OP/BINARY_ADD 执行时,解释器检查两侧操作数的类型对象(type(obj))。

类型槽(C 层):每个类型对象里有一组函数槽(如 PyNumberMethods、PySequenceMethods、tp_richcompare 等)。

例如加法会优先尝试左操作数类型的 nb_add 槽;若返回 NotImplemented,再尝试右操作数类型的反向槽(等价于 radd)。

子类优先:右操作数的类型是左操作数类型的严格子类时,会优先尝试右侧的实现(以便子类覆盖父类行为)。

就地运算:有单独槽(如 nb_inplace_add,对应 iadd);若未实现或返回 NotImplemented,回退到常规二元运算(构造新对象)。

特殊方法查找规则:魔术方法不是普通属性查找(不会从实例字典找),解释器直接在类型对象上找并缓存,以避免递归与保持性能。

比较分派:== < … 通过 tp_richcompare 槽;返回 NotImplemented 可触发对方类型或默认回退(object.eq 基于身份)。

数值塔与快速路径:

int 是任意精度(大整数),指令会先尝试快速小整数路径,溢出再走“大整数”算法。

内置同型操作(如 int+int)直接走内置槽函数(C 实现,避免 Python 级别调用开销)。

协议化扩展:新增运算符(如 @)只需约定新的魔术方法(matmul),解释器在分派时查找对应槽即可,用户类型即可“接入”。

直观理解:Python 把“运算符含义”当成类型协议,运行期根据对象的类型动态决定具体调用哪个实现,并提供回退与反向重载机制。

4 使用场景和示例

选哪种思路

需要可读的数学记号、领域对象直观运算(矩阵/向量/单位/有理数等):Python 的运算符重载天然合适。

追求语义稳定、编译期优化与团队一致风格:Go 的方法/函数式 API 更清晰、可控。

Go + 泛型:可写出“在数值类型间可复用”的库,但不要指望“像 Python 那样给类型装配新语义”。

  • 对比代码

    Go:方法替代运算符
    
    package main
    
    import "fmt"
    
    type Vec2 struct{ X, Y float64 }
    
    func (v Vec2) Add(u Vec2) Vec2   { return Vec2{v.X + u.X, v.Y + u.Y} }
    func (v Vec2) Scale(k float64) Vec2 { return Vec2{v.X * k, v.Y * k} }
    
    func main() {
        v := Vec2{1, 2}.Add(Vec2{3, 4}).Scale(0.5)
        fmt.Println(v) // {2 3}
    }
    

Python:通过魔术方法重载

  class Vec2:
      def __init__(self, x, y): self.x, self.y = x, y
      def __add__(self, o):
          if not isinstance(o, Vec2): return NotImplemented
          return Vec2(self.x + o.x, self.y + o.y)
      def __mul__(self, k):
          if not isinstance(k, (int, float)): return NotImplemented
          return Vec2(self.x * k, self.y * k)
      __radd__ = __add__
      __rmul__ = __mul__
      def __repr__(self): return f"Vec2({self.x}, {self.y})"

  v = (Vec2(1,2) + Vec2(3,4)) * 0.5
  print(v)  # Vec2(2.0, 3.0)

5 小结

“给类型写个方法就能改变 + 的含义?”——不行。方法只能用于显式调用,运算符行为不可更改。

“具名类型是否就等于重载?”——不是。只能沿用底层类型的运算规则,且仅在同一具名类型之间才能直接运算。

“能比较所有东西吗?”——切片、映射、函数不可比较(除与 nil),结构体/数组可比较取决于其字段/元素是否可比较。

Python

  __add__ 返回错误对象而非 NotImplemented 会破坏反向分派;应在“不支持的类型”时返回 NotImplemented。

  __iadd__ 未实现会退化为创建新对象,与“原地修改”语义不同(影响可变/不可变设计)。

	定义了 __eq__ 但忘了配套 __hash__ 可能导致实例不可哈希(集合/字典键问题)。

比较运算建议全部成套实现,避免跨类型比较出现不一致行为。

Go:运算符语义是“编译期固定资产”,用户不可干预。

Python:运算符是“类型协议入口”,通过魔术方法在运行期动态决定、可扩展、可重写并带有完善的回退与反向分派机制。

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。