Python 3 如何用元编程和 typehints 实现类函数重载

Python 是有名的动态类型语言,本身不支持函数重载。

class Foo:
    def add(self, a):
        pass 
def add(self, a, b):
	# 会覆盖上面已经定义类函数
    pass

然而使用元编程 (meta-programming) 和 python 3.5+ 的 type hints,可以实现类函数重载。

然后写了一个库:pover - Python OVERload

pip3 install pover

使用一个 metaclass OverloadMeta实现类函数重载。

#!/usr/bin/env python3
import unittest
import pover

class Foo(metaclass=pover.OverloadMeta): def add(self, a: int): return a + 1

def add(self, a: list):
    return a + [1]

def add(self, a: set):
    a.add(1)
    return a

def minus(self, a: int):
    return a - 1

def minus(self, a: str):
    return a[:-1]

class OverloadTest(unittest.TestCase): def test_overload(self): foo = Foo() self.assertEqual(foo.add(1), 2) self.assertEqual(foo.add([0]), [0, 1]) self.assertEqual(foo.add({0}), {0, 1}) self.assertEqual(foo.minus(1), 0) self.assertEqual(foo.minus(“hello”), “hell”)

具体实现原理在: https://github.com/idf/pover/blob/master/pover/core.py

GitHub 地址,请多指教: https://github.com/idf/pover


Python 3 如何用元编程和 typehints 实现类函数重载

10 回复

在Python里实现真正的函数重载比较麻烦,因为Python不支持像C++那样的原生重载。不过我们可以用functools.singledispatch配合类型提示来模拟。

最简单的方案是用singledispatch装饰器:

from functools import singledispatch
from typing import Union

@singledispatch
def process(data):
    raise NotImplementedError("Unsupported type")

@process.register
def _(data: int):
    return f"Processing integer: {data}"

@process.register  
def _(data: str):
    return f"Processing string: {data}"

@process.register
def _(data: list):
    return f"Processing list with {len(data)} items"

# 使用示例
print(process(42))        # Processing integer: 42
print(process("hello"))   # Processing string: hello  
print(process([1, 2, 3])) # Processing list with 3 items

对于类方法,可以用singledispatchmethod

from functools import singledispatchmethod

class Processor:
    @singledispatchmethod
    def handle(self, data):
        raise NotImplementedError("Unsupported type")
    
    @handle.register
    def _(self, data: int):
        return f"Handling int: {data}"
    
    @handle.register
    def _(self, data: str):
        return f"Handling str: {data}"

如果你想要更花哨的元编程方案,可以自己写装饰器来收集重载函数:

from inspect import signature
from typing import get_type_hints, Dict, Callable, Any

class Overload:
    def __init__(self):
        self._registry: Dict[tuple, Callable] = {}
    
    def register(self, func: Callable):
        sig = signature(func)
        type_hints = get_type_hints(func)
        key = tuple(type_hints.get(param, Any) for param in sig.parameters if param != 'self')
        self._registry[key] = func
        return self
    
    def __call__(self, *args, **kwargs):
        from typing import get_type_hints
        arg_types = tuple(type(arg) for arg in args)
        
        # 寻找匹配的函数
        for key, func in self._registry.items():
            if len(key) == len(arg_types) and all(
                isinstance(arg, expected) if expected is not Any else True
                for arg, expected in zip(arg_types, key)
            ):
                return func(*args, **kwargs)
        raise TypeError("No matching overload found")

# 使用示例
overload_func = Overload()

@overload_func.register
def func(x: int) -> str:
    return f"int: {x}"

@overload_func.register  
def func(x: str) -> str:
    return f"str: {x}"

print(func(10))   # int: 10
print(func("hi")) # str: hi

不过说实话,Python的动态类型特性让重载没那么必要,通常用可选参数或者联合类型就能搞定。

总结:用singledispatch最省事。

了解过,singledispatch 没法 dispatch 类函数吧。singledispatch decorator renames module functions,这个机制并不使用类函数。

from functools import singledispatch


def add(a:object): pass

.register(int)
def _add(a: int): return a + 1

.register(list)
def _add(a: list): return a + [1]

.register(set)
def _add(a: set): a.add(1); return a

cases=[1, [0], {0}]
for case in cases: print(add(case))

实现上由于 python 是动态语言,传递对象是鸭子类型又可以变长参数,所以用上方法重载的机会不太多。
java 那个有名的 23 种设计模式,大约有 16 种设计模式在动态语言里面根本就不需要了。

single dispatch 是不错,但是不适合类函数吧?见#2

当然可以用在类函数了。

show me the code.

Decorator invocation 是在类函数 invocation 时执行的的,类函数 definition 的时候同函数名已经在 class construction 被覆盖了。

当然要静态方法调用啊,直接 类名.方法()

回到顶部