Checking Compatibility Of Two Python Functions (or Methods)
Solution 1:
What would you be basing the compatibility on? The number of arguments? Python has variable length argument lists, so you never know if two functions might be compatible in that sense. Data types? Python uses duck typing, so until you use an isinstance test or similar inside the function, there is no constraint on data types that a compatibility test could be based on.
So in short: No.
You should rather write good docstrings, such that any user of your API knows what the function he is giving you has to do, and then you should trust that the function you get behaves correctly. Any "compatibility" check would either rule out possibly valid functions or give you a false sense of "everything is exactly as it should be."
The pythonic way of exposing an API is: Write good documentation, such that people know what they need to know, and trust that they do the right thing. In critical positions you can still use try: except:
, but anybody who is misusing your API because they just didn't care to read the doc shouldn't be given a false sense of security. And someone who did read your doc and wants to use it in a totally acceptable way should not be denied the possibility to use it on the grounds of the way they declared a function.
Solution 2:
Take a look at inspect.getargspec()
:
inspect.getargspec(func)
Get the names and default values of a function’s arguments. A tuple of four things is returned: (args, varargs, varkw, defaults). args is a list of the argument names (it may contain nested lists). varargs and varkw are the names of the * and ** arguments or None. defaults is a tuple of default argument values or None if there are no default arguments; if this tuple has n elements, they correspond to the last n elements listed in args.
Changed in version 2.6: Returns a named tuple ArgSpec(args, varargs, keywords, defaults).
Solution 3:
Although Python is Dynamically Typed language, but there is a strong notion of types in python (Strongly typed). And after the introduction of type hints it is possible now to check for functions interchangeability. But first let us state the following:
The Liskov substitution principle:
If type t2
is a subtype of type t1
then an object of type t1
should be replaceable by object of type t2
.
The Contra/Covariance of Callable
type:
Callable[[], int]
is a subtype ofCallable[[], float]
(Covariance).This one is intuitive: a callable that eventually evaluate to
int
could replace a function that evaluate tofloat
(ignore the args list for a moment).Callable[[float], None]
is a subtype ofCallable[[int], None]
(Contravariance).this one is a little bit confusing, but remember, a callable that operate on integers may perform an operation that are not defined on floats such as,
>>
<<
, while a callable that operate on float will certainly not perform any operation that is not defined on integers (because integer is subtype of float). So a callable that operates on a float may replace a callable that operates on an integer but not via versa (ignoring return types).
From the above we conclude that: For a callable c1
to be replaceable by callable c2
, the following should satisfy:
c2
return type should be a subtype ofc1
return type.- For argument list of
c1
:(a1, a2,...an)
and the argument list ofc2
:(b1, b2,...bn)
,a1
should bea1
subtypeb1
,a2
subtype ofb2
and so on.
Implementation
Simple implementation would be (ignoring kwargs
and variable length argument lists):
from inspect import getfullargspec
defissubtype(func1, func2):
"""Check whether func1 is a subtype of func2, i.e func1 could replce func2"""
spec1, spec2 = getfullargspec(func1), getfullargspec(func2)
ifnotissubclass(spec1.annotations['return'], spec2.annotations['return']):
returnFalsereturnall((issubclass(spec2.annotations[arg2], spec1.annotations[arg1]) for (arg1, arg2) inzip(spec1.args, spec2.args)))
Examples:
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))
Output:
TrueFalseTrueFalse
Post a Comment for "Checking Compatibility Of Two Python Functions (or Methods)"