Skip to content Skip to sidebar Skip to footer

Checking Compatibility Of Two Python Functions (or Methods)

Is there a possibility to check if two python functions are interchangeable? For instance, if I have def foo(a, b): pass def bar(x, y): pass def baz(x,y,z): pass I wou

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 of Callable[[], float] (Covariance).

    This one is intuitive: a callable that eventually evaluate to int could replace a function that evaluate to float (ignore the args list for a moment).

  • Callable[[float], None] is a subtype of Callable[[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:

  1. c2 return type should be a subtype of c1 return type.
  2. For argument list of c1: (a1, a2,...an) and the argument list of c2: (b1, b2,...bn), a1 should be a1 subtype b1, a2 subtype of b2 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)"