当我们需要比以前多 return 个值时,保持函数的向后兼容性

Keep backward compatibility for a function when we need to return more values than before

我有一个函数,当前 return 有两个值,一个 int 和一个字符串,例如:

def myfunc():
    return 0, 'stringA'

此函数已在很多代码中使用,但我需要对其进行改进,使其 return 具有三个值,一个 int 和两个字符串,例如:

def myfunc():
    return 0, 'stringA', 'stringB'

当然,我想保持与现有代码的兼容性,所以 return像上面修改的函数那样输入值将导致 ValueError。

一种解决方案是将改进后的函数包装到另一个具有旧名称的函数中,因此我们在现有代码中调用初始函数,在新代码中调用新函数,例如:

def newmyfunc():
    return 0, 'A', 'B'

def myfunc():
    result1, result2, _ = newmyfunc()
    return result1, result2

就此解决方案而言,我并不觉得它很优雅。

有没有更好的方法来实现这个目标? 类似于可以 return 两个或三个值而无需修改使用该函数的现有代码的多态函数?

首先,回答一个你没有问过但可能对将来或对其他人有帮助的问题:

当我发现我从单个函数 returning 多个项目时,尤其是当项目列表 returned 开始增长时,我经常发现 return 字典或对象而不是元组。原因是随着 returned-item 列表的增长,跟踪哪个项目在哪个索引处变得更加困难。如果一组 returned 项目将被单独使用并且不是 closely-related 除了来自同一个函数之外,我更喜欢 dict。如果 returned 项 在多个位置一起使用(例如用户名、密码、主机和端口),将它们全部包装在一个对象中(实例化一个自定义 class),然后传递它。 YMMV,听起来你在试图避免重构代码,所以:

解决您的实际问题的最简单方法是向您的函数添加关键字参数,为该参数设置默认值,并使用它来决定 return:[=12= 的参数版本]

def myfunc(return_length=2):
    if return_length == 2:
        return 0, 'stringA'
    elif return_length == 3:
        return 0, 'stringA', 'stringB'
    else:
        raise ValueError(f'Unexpected number of return arguments {return_length}')

旧代码继续调用函数 as-is,新代码显式调用 my_func(return_length=3)。当所有旧代码都被弃用时,您可以将默认值更改为 3 and/or 将其设置为 2 时抛出错误。

装饰器示例:所涉及函数的主体保持不变,“修改”部分委托给外部函数,装饰器

假设“基础函数”不带参数。

def dec(f_reference):
    return lambda f_extra_feature: lambda:(*f_reference(), f_extra_feature())

def myfunc():
    return 0, 'stringA'

def xxxfunc():
    return 'XXX'

myfunc = dec(f_reference=myfunc)(f_extra_feature=xxxfunc)

print(myfunc)
#(0, 'stringA', 'XXX')

根据需要,第二个参数 f_extra_feature 可以隐式设置。

可以使用语法糖表示法来完成不太灵活的修饰

# order of argument is changed!
def dec2(f_extra_feature):
    return lambda f_reference: lambda:(*f_reference(), f_extra_feature())

def xxxfunc():
    return 'XXX'

@dec2(f_extra_feature=xxxfunc)
def myfunc():
    return 0, 'stringA'

print(myfunc())
#(0, 'stringA', 'XXX')

编辑:

def newmyfunc():
    return 0, 'A', 'B'

def replacer(f):
    return lambda f_target: lambda: f()[slice(0, 2)] 

@replacer(newmyfunc)
def myfunc():
    return 0, 'stringA'

# new body of the function, execute newmyfunc
print(myfunc())