如何在python中实现函数重载?

170 views
Skip to first unread message

Mu Ye

unread,
Aug 23, 2019, 12:39:24 AM8/23/19
to python-cn(华蟒用户组,CPyUG 邮件列表)
因为看到有趣的题目,如何在python中实现函数重载达到下面的效果,想要去实现它
@method
def add(a):return a

@method
def add(a,b:int): return a+b

@method
def add(a,b:str):return f"{a}{b}"

@method
def add(a,b,c):return a+b+c

print(add(1,2,3))
print(add(1,2))
print(add(1,"abc"))
print(add(1))


我的解法是
from functools import wraps
map = {}


def method(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        nonlocal func
        hash = (func.__code__.co_argcount, str(func.__annotations__))
        if hash not in map:
            map[hash] = func
        else:
            func = map[hash]
        print(map)
        func(*args, **kwargs)
    return wrapper


@method
def add(a, b: int):
    return a+b

@method
def add(a: int): return a

print(add(1))
print(add(1, 2))



console
{(1, "{'a': <class 'int'>}"): <function add at 0x000001F505702EA0>}
None
{(1, "{'a': <class 'int'>}"): <function add at 0x000001F505702EA0>}
Traceback (most recent call last):
  File ".\test.py", line 27, in <module>
    print(add(1, 2))
  File ".\test.py", line 15, in wrapper
    func(*args, **kwargs)
TypeError: add() takes 1 positional argument but 2 were given


 


得到的结果却并不正确?是我哪里写错了吗?

李者璈

unread,
Aug 23, 2019, 2:54:49 AM8/23/19
to pyth...@googlegroups.com
这里的问题是,装饰器本质是一个替换过程

这里是 `add = method(add)`

所以在你最后的代码里面,wraps 中使用的 func 实际上是最后一次使用装饰器的函数,所以这里就会出问题

在 Python 3.4 以后,如果是同样的参数个数,不通的参数类型的话,可以使用, https://docs.python.org/3/library/functools.html#functools.singledispatch

如果是类中对 method 进行重载的话,可以考虑元类


李者璈 & Zheaoli




Mu Ye <m393...@gmail.com> 于2019年8月23日周五 下午12:39写道:
--
邮件来自: `CPyUG`华蟒用户组(中文Python技术邮件列表)
规则: http://code.google.com/p/cpyug/wiki/PythonCn
详情: http://code.google.com/p/cpyug/wiki/CpyUg
严正: 理解列表! 智慧提问! http://wiki.woodpecker.org.cn/moin/AskForHelp
---
您收到此邮件是因为您订阅了Google网上论坛上的“python-cn(华蟒用户组,CPyUG 邮件列表)”群组。
要退订此群组并停止接收此群组的电子邮件,请发送电子邮件到python-cn+...@googlegroups.com
要在网络上查看此讨论,请访问https://groups.google.com/d/msgid/python-cn/83453fc0-b8de-4e3c-a582-7e88b9b1e5ce%40googlegroups.com

liu liqiu

unread,
Aug 23, 2019, 4:15:02 AM8/23/19
to pyth...@googlegroups.com
你的想法应该是根据 args 和 kwargs 从 map中选择函数。
现在实际上根据最后一次定义的函数的签名选择函数,也就是实际上每次调用的都是最后一次定义的函数。

李者璈 <lizhea...@gmail.com> 于2019年8月23日周五 下午2:54写道:

vicalloy

unread,
Aug 23, 2019, 4:26:53 AM8/23/19
to pyth...@googlegroups.com
原因上面的回复已经说了。稍微调了一下,下面的可以跑通。

from functools import wraps


def method(func, map={}):
   
    arg_type = ''
    if func.__annotations__:
        arg_type = list(func.__annotations__.values())[0]
    key = (func.__code__.co_argcount, str(arg_type))
    map[key] = func

    @wraps(func)
    def wrapper(*args):
        key = (len(args), str(type(args[0])))
        func = map[key]
        return func(*args)

    return wrapper



--

Jeffrey Zhang

unread,
Aug 27, 2019, 4:07:36 AM8/27/19
to pyth...@googlegroups.com
通过 metadata class 可以实现, 具体参见 http://xcodest.me/python-metaclass-magic.html



--
Regards,
Jeffrey Zhang

ysw

unread,
Aug 27, 2019, 11:39:34 AM8/27/19
to python-cn(华蟒用户组,CPyUG 邮件列表)
感觉很好玩... 

按照 LZ 的思路(使用 decorator + mapping)其实是可以实现的,不过 LZ 的实现存在两个问题
1. 计算 hash 的方式不是很优雅,我觉得使用 Python 的函数签名相关库来实现会比较方便, 相对也会更鲁棒
2. 应该在外层保存 hash 与 func 的对应关系,而不是在 wrapper 函数里面
3. 在 wrapper 函数里面,应该取一个符合函数签名的函数,而 LZ 没有这个逻辑

下面是我基于楼主的修改版本

import inspect
from functools import wraps


mapping = {}  # sig: func


def sig_match(sig, args, kwargs):
    try:
        sig.bind(*args, **kwargs)
    except TypeError:
        return False
    return True


def method(func):
    sig = inspect.signature(func)
    mapping[sig] = func

    @wraps(func)
    def wrapper(*args, **kwargs):
        real_func = None
        for sig in mapping:
            if sig_match(sig, args, kwargs):
                real_func = mapping[sig]
                break
        else:
            raise RuntimeError('no such func')
        return real_func(*args, **kwargs)
    return wrapper


@method
def add(a, b: int):
    return a+b


@method
def add(a: int): return a


print(add(1))
print(add(1, 2))
print(add(1, 2, 3))


###### tests

/tmp > python3 t.py
1
3
Traceback (most recent call last):
  File "t.py", line 44, in <module>
    print(add(1, 2, 3))
  File "t.py", line 28, in wrapper
    raise RuntimeError('no such func')
RuntimeError: no such func
Reply all
Reply to author
Forward
0 new messages