【问题标题】:Checking compatibility of two python functions (or methods)检查两个 python 函数(或方法)的兼容性
【发布时间】:2010-11-04 13:27:20
【问题描述】:

是否有可能检查两个 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,所以我可以在实际调用它们之前检查它们是否可以互换。

【问题讨论】:

  • “兼容”是什么意思? def quux(*args) 适合哪里?

标签: python reflection


【解决方案1】:

看看inspect.getargspec()

inspect.getargspec(func)

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

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

【讨论】:

  • 哦,非常好...正是我需要的。谢谢!
【解决方案2】:

您将基于什么兼容性?参数的数量? Python 有可变长度的参数列表,所以你永远不知道两个函数在这个意义上是否兼容。数据类型? Python 使用鸭子类型,因此在您在函数内部使用 isinstance 测试或类似测试之前,兼容性测试可以基于的数据类型没有限制。

简而言之:没有。

您应该编写好的文档字符串,这样您的 API 的任何用户都知道他给您的函数必须做什么,然后您应该相信您获得的函数行为正确。任何“兼容性”检查要么排除可能的有效功能,要么给你一种“一切都应该是正确的”的错误感觉。

公开 API 的 Python 方式是:编写好的文档,让人们知道他们需要知道什么,并相信他们做的是正确的事情。在关键职位上你仍然可以使用try: except:,但是任何因为不关心阅读文档而滥用你的 API 的人都不应该被赋予错误的安全感。读过您的文档并希望以完全可接受的方式使用它的人不应因为他们声明函数的方式而被拒绝使用它的可能性。

【讨论】:

  • 是的,参数数量。我知道我不能检查更多,但我认为模块的用户尽早拒绝给定的方法/函数是件好事(这是一个简单的事件处理机制)
  • 但是这样你会拒绝一个可能完全有效的函数,因为它被声明为 def func(*args) 或 def func(a,b,foo="default")。请记住这一点。
  • @balpha:这也可以使用 inspect.getargspec 进行检查。它报告是否有 *args 和 **kwargs 参数。
  • 是的,但您最终会得到任何信息。我已经编辑了我的答案,希望能让我的陈述更清楚。
  • @adnanmuttaleb 可能,但这个答案来自 2009 年 ? 也许发布一个将现代 Python 考虑在内的新答案。
【解决方案3】:

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

Liskov substitution principle

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

Callable 类型的反/协方差:

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

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

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

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

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

  1. c2 返回类型应该是 c1 返回类型的子类型。
  2. 对于c1的参数列表:(a1, a2,...an)c2的参数列表:(b1, b2,...bn)a1应该是a1子类型b1a2@98765434的子类型等等开。

实施

简单的实现是(忽略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)))

例子:

from numbers import Integral, Real


def c1(x :Integral) -> Real: 
  pass

def c2(x: Real) -> Integral:
  pass

print(issubtype(c2, c1))
print(issubtype(c1, c2))

class Employee:
  pass

class Manager(Employee):
  pass

def emp_salary(emp :Employee) -> Integral:
  pass

def man_salary(man :Manager) -> Integral:
  pass

print(issubtype(emp_salary, man_salary))
print(issubtype(man_salary, emp_salary))

输出:

True
False
True
False

【讨论】:

    猜你喜欢
    • 2023-01-31
    • 2020-01-21
    • 2021-12-12
    • 1970-01-01
    • 2015-06-06
    • 2016-11-13
    • 2018-07-29
    • 2019-02-08
    • 1970-01-01
    相关资源
    最近更新 更多