Checking compatibility of two python functions (or methods)

余生长醉 提交于 2020-08-08 04:08:46

问题


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 would like a function is_compatible(a,b) that returns True when passed foo and bar, but False when passed bar and baz, so I can check if they're interchangeable before actually calling either of them.


回答1:


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).




回答2:


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.




回答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

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)))

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:

True
False
True
False


来源:https://stackoverflow.com/questions/1022124/checking-compatibility-of-two-python-functions-or-methods

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!