检查两个python函数(或方法)的兼容性

时间:2009-06-20 17:57:48

标签: python reflection

是否有可能检查两个python函数是否可以互换?例如,如果我有

def foo(a, b):
    pass
def bar(x, y):
    pass
def baz(x,y,z):
    pass

我希望函数is_compatible(a,b)在传递foo和bar时返回True,但是当传递bar和baz时返回False,所以我可以在实际调用它们之前检查它们是否可以互换。

3 个答案:

答案 0 :(得分:4)

你会以兼容性为基础做什么?争论的数量? Python有可变长度的参数列表,所以你永远不知道两个函数在这个意义上是否兼容。数据类型? Python使用duck typing,因此在函数内部使用isinstance测试或类似函数之前,对兼容性测试可能基于的数据类型没有约束。

简而言之:不。

你应该写好文档字符串,这样你的API的任何用户都知道他给你的功能是什么,然后你应该相信你得到的功能正常。任何“兼容性”检查都会排除可能有效的功能,或者让你误解“一切都应该如此。”

暴露API的pythonic方式是:编写好的文档,以便人们知道他们需要知道什么,并相信他们做正确的事情。在关键位置,您仍然可以使用try: except:,但任何滥用您的API的人,因为他们只是不关心阅读文档,不应该给予错误的安全感。而且,如果某人确实阅读了您的文档,并希望以完全可接受的方式使用它,则不应该因为他们声明函数的方式而使用它的可能性。

答案 1 :(得分:3)

看看inspect.getargspec()

  

inspect.getargspec(func)

     

获取姓名   和函数的默认值   参数。四件事的元组是   返回:(args,varargs,varkw,   缺省值)。 args是一个列表   参数名称(可能包含嵌套的名称)   列表)。 varargs和varkw是   *和**参数的名称或   没有。 defaults是默认元组   参数值,如果有,则为None   没有默认参数;如果这个元组   有n个元素,它们对应于   args中列出的最后n个元素。

     

在2.6版中更改:返回a   命名为元组ArgSpec(args,varargs,   关键字,默认值。

答案 2 :(得分:1)

尽管Python是动态类型语言,但是python中有很强的类型概念(严格类型)。在引入类型提示之后,现在可以检查函数的可互换性。但是首先让我们声明以下内容:

Liskov substitution principle

如果类型t2是类型t1的子类型,则类型t1的对象应该可以被类型t2的对象替换。

Callable类型的对比度/协方差:

  • Callable[[], int]Callable[[], float](协方差)的子类型。

    这很直观:一个最终评估为int的可调用对象可以替换一个评估为float的函数(暂时忽略args列表)。

  • Callable[[float], None]Callable[[int], None](Contravariance)的子类型。

    这有点令人困惑,但是请记住,对整数进行操作的可调用对象可能会执行未在浮点数上定义的操作,例如>> <<,而对float当然不会执行未在整数上定义的任何操作(因为integer是float的子类型)。因此,在浮点数上操作的可调用对象可以替换在整数上但不能通过整数操作的可调用对象(忽略返回类型)。

从以上我们可以得出结论:对于要由可调用c1代替的可调用c2,应满足以下条件:

  1. c2返回类型应该是c1返回类型的子类型。
  2. 对于c1(a1, a2,...an)的参数列表和c2(b1, b2,...bn)的参数列表,a1应该是a1子类型{{ 1}},b1的{​​{1}}子类型,依此类推。

实施

简单的实现将是(忽略a2和可变长度参数列表):

b2

示例:

kwargs

输出:

from inspect import getfullargspec

def issubtype(func1, func2):
  """Check whether func1 is a subtype of func2, i.e func1 could replce func2"""
  spec1, spec2 = getfullargspec(func1), getfullargspec(func2)
  
  if not issubclass(spec1.annotations['return'], spec2.annotations['return']):
    return False
  
  return all((issubclass(spec2.annotations[arg2], spec1.annotations[arg1]) for (arg1, arg2) in zip(spec1.args, spec2.args)))