expanding dictionary to function arguments

Discussion in 'Python' started by Noah, Nov 2, 2005.

  1. Noah

    Noah Guest

    I have a dictionary that I would like to expand to satisfy a
    function's agument list. I can used the ** syntax to pass a dictionary,
    but
    this only works if each key in the dictionary matches an argument.
    I cannot pass a dictionary that has more keys than the function has
    arguments.

    # Example 1 - This works:
    # Prints "hello world!"
    def foo (arg1='greetings', arg2='planet', arg3='.'):
    print arg1 + ' ' + arg2 + arg3
    args = {'arg1':'hello', 'arg2':'world', 'arg3':'!'}
    foo (**args)

    # Example 2 - This does not work:
    # raises TypeError: foo() got an unexpected keyword argument 'arg4')
    def foo (arg1='greetings', arg2='planet', arg3='.'):
    print arg1 + ' ' + arg2 + arg3
    args = {'arg1':'hello', 'arg2':'world', 'arg3':'!', 'arg4':'ignore'}
    foo (**args)

    As a practical application, I have a project where I have a config file

    that defines a large number of keys and values. I read the config
    file into a dictionary called "options". I also have an API module with
    many
    functions that I want to call with arguments taken directly from the
    "options" dictionary. The key names in the "options" dictionary match
    the argument names of the functions in my API.

    # The ugly, brutish way:
    options = read_config ("options.conf")
    extract_audio (options['source_video_filename'])
    compress_audio (options['audio_raw_filename'],
    options['audio_compressed_filename'], options['audio_sample_rate'],
    options['audio_bitrate'])
    mux (options['source_video_filename'],
    options['audio_compressed_filename'], options['output_video_filename'])

    I know that the keys in my "options" dictionary match the arguments
    of the functions in the API library, so I would like to do this:
    options = read_config ("options.conf")
    extract_audio (**options)
    compress_audio (**options)
    mux (**options)

    I created the following function to do what I am describing.
    This isn't too bad, but I thought that perhaps there was some
    secret Python syntax that will do this for me.

    def apply_smart (func, args):
    """This is similar to func(**args), but this won't complain about
    extra keys in 'args'. This ignores keys in 'args' that are
    not required by 'func'. This passes None to arguments that are
    not defined in 'args'. That's fine for arguments with a default
    valeue, but
    that's a bug for required arguments. I should probably raise a
    TypeError.
    """
    if hasattr(func,'im_func'): # Handle case when func is a class
    method.
    func = func.im_func
    argcount = func.func_code.co_argcount
    required_args = dict([(k,args.get(k)) for k in
    func.func_code.co_varnames[:argcount]])
    return func(**required_args)

    So, I now I can do this:
    options = read_config ("options.conf")
    apply_smart (extract_audio, options)
    apply_smart (compress_audio, options)
    apply_smart (mux, options)

    Neat, but is that the best I can do?

    Yours,
    Noah
    Noah, Nov 2, 2005
    #1
    1. Advertising

  2. Noah a écrit :
    > I have a dictionary that I would like to expand to satisfy a
    > function's agument list. I can used the ** syntax to pass a dictionary,
    > but
    > this only works if each key in the dictionary matches an argument.
    > I cannot pass a dictionary that has more keys than the function has
    > arguments.


    If you have control over the API functions declarations, makes them so:

    def my_api_func(arg1='', arg2='whatever', **kwargs):
    code_here
    Bruno Desthuilliers, Nov 2, 2005
    #2
    1. Advertising

  3. Noah

    Noah Guest

    Bruno Desthuilliers a écrit :
    > Noah a écrit :
    > If you have control over the API functions declarations, makes them so:
    > def my_api_func(arg1='', arg2='whatever', **kwargs):
    > code_here


    Unfortunately I cannot change the API functions.
    I should have mentioned that.

    Yours,
    Noah
    Noah, Nov 2, 2005
    #3
  4. Noah wrote:
    > Bruno Desthuilliers a écrit :
    >
    >>Noah a écrit :
    >>If you have control over the API functions declarations, makes them so:
    >>def my_api_func(arg1='', arg2='whatever', **kwargs):
    >> code_here

    >
    >
    > Unfortunately I cannot change the API functions.
    > I should have mentioned that.


    Yeps... That what I thought, but you didn't mention it, and it was not
    that clear (or it's me being stupid...).

    I'm no great expert, but apart from writing a generic decorator for the
    API objects and decorating them all with your smart_apply function -
    which won't by you much in this case imho -, I don't see a much better
    solution...

    Some guru around ???

    --
    bruno desthuilliers
    python -c "print '@'.join(['.'.join([w[::-1] for w in p.split('.')]) for
    p in ''.split('@')])"
    bruno at modulix, Nov 2, 2005
    #4
  5. On 1 Nov 2005 17:17:00 -0800, "Noah" <> wrote:

    >I have a dictionary that I would like to expand to satisfy a
    >function's agument list. I can used the ** syntax to pass a dictionary,
    >but
    >this only works if each key in the dictionary matches an argument.
    >I cannot pass a dictionary that has more keys than the function has
    >arguments.

    [...]
    >I created the following function to do what I am describing.
    >This isn't too bad, but I thought that perhaps there was some
    >secret Python syntax that will do this for me.
    >
    >def apply_smart (func, args):
    > """This is similar to func(**args), but this won't complain about
    > extra keys in 'args'. This ignores keys in 'args' that are
    > not required by 'func'. This passes None to arguments that are
    > not defined in 'args'. That's fine for arguments with a default
    >valeue, but
    > that's a bug for required arguments. I should probably raise a
    >TypeError.
    > """

    Ok, so why not do it? ;-)
    > if hasattr(func,'im_func'): # Handle case when func is a class
    >method.
    > func = func.im_func

    skipself = True
    else: skipself = False
    > argcount = func.func_code.co_argcount

    Make arg list and call with it instead of
    > required_args = dict([(k,args.get(k)) for k in
    >func.func_code.co_varnames[:argcount]])
    > return func(**required_args)

    try:
    required_args = [args[k] for k in func.func_code.co_varnames[skipself:argcount]]
    except KeyError:
    raise TypeError, '%s(...) missing arg %r'%(func.func_name, k)
    return func(*required_args)

    >
    >So, I now I can do this:
    > options = read_config ("options.conf")
    > apply_smart (extract_audio, options)
    > apply_smart (compress_audio, options)
    > apply_smart (mux, options)
    >
    >Neat, but is that the best I can do?
    >

    I suppose you could replace your local bindings of extract_audio, compress_audio, and mux
    with wrapper functions of the same name that could cache the func and arg names in closure
    variables, e.g., using a decorator function (virtually untested)

    def call_with_args_from_dict(func):
    argnames = func.func_code.co_varnames[hasattr(func, 'im_func'):func.func_code.co_argcount]
    ndefaults = len(func.func_defaults or ())
    if ndefaults:
    defnames = argnames[-ndefaults:]
    argnames = argnames[:-ndefaults]
    else:
    defnames = []
    def _f(**args):
    try:
    actualargs = [args[argname] for argname in argnames]
    for argname in defnames:
    if argname not in args: break
    actualargs.append(args[argname])
    except KeyError: raise TypeError, '%s(...) missing arg(s) %r'%(
    func.func_name, [argname for argname in argnames if argname not in args])
    return func(*actualargs)
    _f.func_name = func.func_name
    return _f


    and then wrap like

    extract_audio = call_with_args_from_dict(extract_audio)

    or use as a decorator if you are defining the function to be wrapped, e.g.,

    @call_with_args_from_dict
    def mux(firstarg, second, etc):
    ...

    Regards,
    Bengt Richter
    Bengt Richter, Nov 2, 2005
    #5
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Neo
    Replies:
    10
    Views:
    635
    sushant
    Jan 20, 2005
  2. jmborr
    Replies:
    1
    Views:
    388
    Stargaming
    Nov 3, 2007
  3. Witold Rugowski

    Expanding array as function arguments

    Witold Rugowski, Dec 5, 2007, in forum: Ruby
    Replies:
    5
    Views:
    91
    MonkeeSage
    Dec 5, 2007
  4. Wolfgang Maier
    Replies:
    0
    Views:
    61
    Wolfgang Maier
    Apr 24, 2013
  5. Fábio Santos
    Replies:
    0
    Views:
    55
    Fábio Santos
    Apr 24, 2013
Loading...

Share This Page