Хотя Python является динамически типизированным языком, в нем существует строгое понятие типов (строго типизированный). А после введения типовых подсказок появилась возможность проверять взаимозаменяемость функций. Но прежде скажем следующее:
принцип подстановки Лисков:
Если тип t2 является подтипом типа t1, то объект типа t1 должен заменяться объектом типа t2.
Контра/Ковариация типа Callable:
Callable[[], int] является подтипом Callable[[], float] (ковариация).
Это интуитивно понятно: вызываемый объект, который в конечном итоге оценивается как int, может заменить функцию, которая оценивается как float (на мгновение игнорируйте список аргументов).
Callable[[float], None] является подтипом Callable[[int], None] (контравариантность).
это немного сбивает с толку, но помните, вызываемый объект, который работает с целыми числами, может выполнять операцию, которая не определена для чисел с плавающей запятой, например, >> <<, в то время как вызываемый объект, работающий с плавающей запятой, определенно не будет выполнять какую-либо операцию, которая не определена. для целых чисел (поскольку целое число является подтипом числа с плавающей запятой). Таким образом, вызываемый объект, работающий с числом с плавающей запятой, может заменить вызываемый объект, работающий с целым числом, но не наоборот (игнорируя возвращаемые типы).
Из вышеизложенного мы заключаем, что: Для того, чтобы вызываемый c1 можно было заменить вызываемым c2, должно удовлетворяться следующее:
- Тип возвращаемого значения
c2 должен быть подтипом возвращаемого значения c1.
- Для списка аргументов
c1: (a1, a2,...an) и списка аргументов c2: (b1, b2,...bn), a1 должно быть a1 подтипом b1, 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)))
Примеры:
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
person
adnanmuttaleb
schedule
28.07.2020
def quux(*args)? - person S.Lott   schedule 20.06.2009