虽然 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,应满足以下条件:
-
c2 返回类型应该是 c1 返回类型的子类型。
- 对于
c1的参数列表:(a1, a2,...an)和c2的参数列表:(b1, b2,...bn),a1应该是a1子类型b1,a2@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