开发者

python check if function accepts **kwargs

is there a way to check if a function accepts **kwargs before calling it e.g.

def FuncA(**kwargs):
    print 'ok'

def FuncB(id = None):
    print 'ok'

def FuncC():
    print 'ok'

args = {'id': '1'}

FuncA(**args)
FuncB(**args)
FuncC(**args)

When I run this FuncA and FuncB would be okay but FuncC errors with got an unexpected keyword argument 'id' 开发者_StackOverflowas it doesn't accept any arguments


try:
    f(**kwargs)
except TypeError:
    #do stuff

It's easier to ask forgiveness than permission.


def foo(a, b, **kwargs):
  pass

import inspect
args, varargs, varkw, defaults = inspect.getargspec(foo)
assert(varkw=='kwargs')

This only works for Python functions. Functions defined in C extensions (and built-ins) may be tricky and sometimes interpret their arguments in quite creative ways. There's no way to reliably detect which arguments such functions expect. Refer to function's docstring and other human-readable documentation.


func is the function in question.

with python2, it's:

inspect.getargspec(func).keywords is not None

python3 is a bit tricker, following https://www.python.org/dev/peps/pep-0362/ the kind of parameter must be VAR_KEYWORD

Parameter.VAR_KEYWORD - a dict of keyword arguments that aren't bound to any other parameter. This corresponds to a "**kwargs" parameter in a Python function definition.

any(param for param in inspect.signature(func).parameters.values() if param.kind == param.VAR_KEYWORD)


For python > 3 you should to use inspect.getfullargspec.

import inspect

def foo(**bar):
    pass

arg_spec = inspect.getfullargspec(foo)
assert arg_spec.varkw and arg_spec.varkw == 'bar'


Seeing that there are a multitude of different answers in this thread, I thought I would give my two cents, using inspect.signature().

Suppose you have this method:

def foo(**kwargs):

You can test if **kwargs are in this method's signature:

import inspect

sig = inspect.signature(foo)
params = sig.parameters.values()
has_kwargs = any([True for p in params if p.kind == p.VAR_KEYWORD])

More

Getting the parameters in which a method takes is also possible:

import inspect

sig = inspect.signature(foo)
params = sig.parameters.values()
for param in params:
    print(param.kind)

You can also store them in a variable like so:

kinds = [param.kind for param in params]

# [<_ParameterKind.VAR_KEYWORD: 4>]

Other than just keyword arguments, there are 5 parameter kinds in total, which are as follows:

POSITIONAL_ONLY        # parameters must be positional

POSITIONAL_OR_KEYWORD  # parameters can be positional or keyworded (default)

VAR_POSITIONAL         # *args

KEYWORD_ONLY           # parameters must be keyworded 

VAR_KEYWORD            # **kwargs

Descriptions in the official documentation can be found here.

Examples

POSITIONAL_ONLY

def foo(a, /): 
# the '/' enforces that all preceding parameters must be positional

foo(1) # valid
foo(a=1) #invalid

POSITIONAL_OR_KEYWORD

def foo(a):
# 'a' can be passed via position or keyword
# this is the default and most common parameter kind

VAR_POSITIONAL

def foo(*args):

KEYWORD_ONLY

def foo(*, a):
# the '*' enforces that all following parameters must by keyworded

foo(a=1) # valid
foo(1) # invalid

VAR_KEYWORD

def foo(**kwargs):


It appears that you want to check whether the function receives an 'id' keyword argument. You can't really do that by inspection because the function might not be a normal function, or you might have a situation like that:

def f(*args, **kwargs):
    return h(*args, **kwargs)

g = lambda *a, **kw: h(*a, **kw)

def h(arg1=0, arg2=2):
    pass

f(id=3) still fails

Catching TypeError as suggested is the best way to do that, but you can't really figure out what caused the TypeError. For example, this would still raise a TypeError:

def f(id=None):
     return "%d" % id

f(**{'id': '5'})

And that might be an error that you want to debug. And if you're doing the check to avoid some side effects of the function, they might still be present if you catch it. For example:

class A(object):
   def __init__(self): self._items = set([1,2,3])
   def f(self, id): return self._items.pop() + id

a = A()
a.f(**{'id': '5'})

My suggestion is to try to identify the functions by another mechanism. For example, pass objects with methods instead of functions, and call only the objects that have a specific method. Or add a flag to the object or the function itself.


According to https://docs.python.org/2/reference/datamodel.html you should be able to test for use of **kwargs using co_flags:

>>> def blah(a, b, kwargs):
...     pass

>>> def blah2(a, b, **kwargs):
...     pass

>>> (blah.func_code.co_flags & 0x08) != 0
False
>>> (blah2.func_code.co_flags & 0x08) != 0
True

Though, as noted in the reference this may change in the future, so I would definitely advise to be extra careful. Definitely add some unit tests to check this feature is still in place.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜