1

I'm trying to understand the following about dealing with functions and their arguments:

def print_my_arg(func, *args, **kwargs):
    func(*args, **kwargs)
    if 'my_arg' in kwargs: 
        print('  my_arg = {}'.format(kwargs['my_arg']))

def foo(call_no, my_arg='Default Value'):
    print('call_no = {}'.format(call_no) )

print_my_arg(foo, 0, my_arg='My Value 1')
print_my_arg(foo, 1, 'My Value 2')
print_my_arg(foo, 2)

Output:

call_no = 0
  my_arg = My Value 1
call_no = 1 # I'd like to see 'My Value 2' here
call_no = 2 # I'd like to see 'Default Value' here

Obviously people are free to invoke functions in either of the ways shown above, which makes me wonder: why my_arg doesn't go to kwargs anyway? Isn't there a uniform way to access parameters by name (and not by position), which doesn't depend on the way the function was invoked?

Please note that:

  1. I'm not interested in print_my_args(func, call_no, my_arg), because I'm talking about the case where I don't know the signature of func in advance and yet I want to know if a particular parameter exists (by name).

  2. Clearly that's related to decorators, but I've written the example in a simpler way (or I hope so).

EDIT

Many thanks for the answers about inspect.signature. Using that, my new version of print_my_arg() is:

from inspect import signature

def print_my_arg ( func, *args, **kwargs ):
  func ( *args, **kwargs )
  sig = signature ( func )
  if 'my_arg' not in sig.parameters: return

  binding = sig.bind ( *args, **kwargs )
  binding.apply_defaults ()

  print ( "  my_arg = {}".format ( binding.arguments [ 'my_arg' ] ) )
3
  • 1
    Positional arguments don't "go to" **kwargs, because they're not keyword arguments. If you want to pass the second positional argument to the function, you'll have to do that explicitly, it's not at all clear why you expected different behaviour. You could force keyword arguments if you really wanted to, see e.g. stackoverflow.com/a/37829651/3001761 Commented Jun 19, 2016 at 17:10
  • 2
    do you want to inspect the signature of the function to check the argument name in print_my_args? then you could do passed_args = inspect.signature(func).bind(*args,**kwargs) then check if my_arg is in passed_args. Commented Jun 19, 2016 at 17:13
  • stackoverflow.com/questions/3394835/args-and-kwargs Commented Jun 19, 2016 at 17:19

1 Answer 1

3

Isn't there a uniform way to access parameters by name (and not by position), which doesn't depend on the way the function was invoked?

Yes by inspecting the signature:

>>> import inspect
>>> sig = inspect.signature(foo)
>>> print(sig)
(call_no, my_arg='Default Value')
>>> args = sig.bind(1, "my_value")
>>> args.arguments["my_arg"]
'my_value'

Note that trying to bind the signature to an invalid call will raise similar/same TypeError that would be raised when calling the function with invalid arguments. Also arguments that use the default will not be present in args.arguments unless you call args.apply_defaults()

Also note that keyword only arguments will be in args.kwargs dictionary instead of args.arguments:

import inspect

def bar(a,*, b=None):
    pass

sig = inspect.signature(bar)

binding = sig.bind(1, b=5)

assert "a" in binding.arguments
assert "b" in binding.kwargs
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.