Check if a function can be called using other function arguments

How to check whether it is always possible to call a function with the same arguments as another function? For example, you can call with all the arguments provided . b a

def a(a, b, c=None):
    pass

def b(a, *args, d=4,**kwargs): 
    pass

I want to have a basic function:

def f(a, b):
    print('f', a, b)

and callback list:

def g(b, a):
    print('g', a, b)

def h(*args, **kwargs):
    print('h', args, kwargs)    

funcs = [g, h]

and a wrapper function that accepts something:

def call(*args, **kwargs):
    f(*args, **kwargs)
    for func in funcs:
        func(*args, **kwargs)

Now I want to check if all callbacks will accept the arguments provided call()if they are valid for f(). For performance reasons, I don’t want to check the arguments every time I call call(), but check every callback before adding it to the callback list. For example, these calls are in order:

call(1, 2)
call(a=1, b=3)

, g :

call(1, b=3)
+4
2

, , . , python 2 .

, ( ) .

. .

, b a. ( ). Uncomment/add try, , true/valse, AssertionError.

import inspect

def check_arg_spec(a,b):
    """

    attrs of FullArgSpec object:

        sp.args = pos or legacy keyword arguments, w/ keyword at back
        sp.varargs = *args
        sp.varkw = **kwargs
        sp.defaults = default values for legacy keyword arguments @ 
        sp.args
        sp.kwdonly = keyword arguments follow *args or *, must be passed in by name
        sp.kwdonlydefaults = {name: default_val, .... }
        sp.annotatons -> currently not in use, except as standard flag for outside applications

    Consume order:

    (1) Positional arguments
    (2) legacy keyword argument = default (can be filled by both keyword or positional parameter)
    [
        (3) *args
        [
          (4) keyword only arguments [=default]
        ]
    ]
    (5) **kwds

    """
    a_sp = inspect.getfullargspec(a) 
    b_sp = inspect.getfullargspec(b)
    kwdfb = b_sp.kwonlydefaults or {}
    kwdfa = a_sp.kwonlydefaults or {}
    kwddefb = b_sp.defaults or []
    kwddefa = a_sp.defaults or []

    # try:
    akwd = a_sp.kwonlyargs
    if len(kwddefa):
        akwd += a_sp.args[-len(kwddefa):]
    bkwd = b_sp.kwonlyargs
    if len(kwddefb):
        bkwd += b_sp.args[-len(kwddefb):]


    # all required arguments in b must have name match in a spec.
    for bkey in (set(b_sp.args) ^ set(bkwd)) & set(b_sp.args) :
        assert bkey in a_sp.args


    # all required positional in b can be met by a
    assert (len(a_sp.args)-len(kwddefb)) >= (len(b_sp.args)-len(kwddefb))

    # if a has  *args spec, so must b
    assert not ( a_sp.varargs and b_sp.varargs is None )


    # if a does not take *args, max number of pos args passed to a is len(a_sp.args). b must accept at least this many positional args unless it can consume *args
    if b_sp.varargs is None:
        # if neither a nor b accepts *args, check that total number of pos plus py2 style keyword arguments for sg of b is more than a can send its way. 
        assert len(a_sp.args) <= len(b_sp.args)


    #  Keyword only arguments of b -> they are required, must be present in a.
    akws = set(a_sp.kwonlyargs) | set(a_sp.args[-len(kwddefa):])

    for nmreq in (set(b_sp.kwonlyargs)^set(kwdfb)) & set(b_sp.kwonlyargs):
         assert nmreq in akws

     # if a and b both accept an arbitrary number of positional arguments or if b can but a cannot, no more checks neccessary here

    # if a accepts optional arbitrary, **kwds, then so must b
    assert not (a_sp.varkw and b_sp.varkw is None)

    if b_sp.varkw is None:
        # neither a nor b can consume arbitrary keyword arguments
        # then b must be able to consume all keywords that a can be called w/th.
        for akw in akwd:
            assert akw in bkwd

          # if b accepts **kwds, but not a, then there is no need to check further
          # if both accept **kwds, then also no need to check further

    #     return True 
    # 
    # except AssertionError:
    # 
    #       return False
+2

, , , , :

from inspect import getargspec

def foo(a, b, c=None):
    pass

def bar(a, d=4, *args, **kwargs):
    pass

def same_args(func1, func2):
    return list(set(getargspec(func1)[0]).intersection(set(getargspec(func2)[0])))

print same_args(foo, bar)
# => ['a']

same_args func1 func2 func1, func2.

+1

Source: https://habr.com/ru/post/1681855/


All Articles