6
\$\begingroup\$

I'm using Python 3.6. This code, used as a decorator to a python function, will cause the function to return a dict of POSITION_OR_KEYWORD arguments.

I used this to create a standard interface to a factory function.

Inspired by this Stack Overflow post, and implemented using a dictionary-flattening function from this previous post.

Eg.

def example(one=1, two=2):
    return {'one':one, 'two':two}

is equivalent to:

@return_kwargs
def example(one=1, two=2):
    pass

import inspect
from functools import wraps
from itertools import chain, starmap

def flatten_dict(dictionary):
    """Flatten a nested dictionary structure"""

    def unpack(parent_key, parent_value):
        """Unpack one level of nesting in a dictionary"""
        try:
            items = parent_value.items()
        except AttributeError:
            # parent_value was not a dict, no need to flatten
            yield (parent_key, parent_value)
        else:
            for key, value in items:
                yield (key, value)

    # Put each key into a tuple to initiate building a tuple of subkeys
    dictionary = {key: value for key, value in dictionary.items()}

    # Keep unpacking the dictionary until all value's are not dictionary's
    dictionary = dict(chain.from_iterable(starmap(unpack, dictionary.items())))
    return dictionary

def return_kwargs(func):
    """Cause the wrapped function to return a dict of keyword arguments"""
    @wraps(func)
    def wrap(*args, **kwargs):
        bound_arguments = inspect.signature(func).bind(*args, **kwargs)
        bound_arguments.apply_defaults()
        return flatten_dict(dict(bound_arguments.arguments))
    return wrap

Let me know how you would implement the idea, whats ugly, corner cases I have missed.

\$\endgroup\$
  • \$\begingroup\$ I don't get it. What is the point of flattening arguments? And why did you change the previous function to flatten a single level? \$\endgroup\$ – 409_Conflict Aug 27 '17 at 13:20
  • \$\begingroup\$ It would do this otherwise: @return_kwargs def example(one=1, two=2, **kwargs): pass example(**example()) Out[9]: {'kwargs': {'kwargs': {}}, 'one': 1, 'two': 2} \$\endgroup\$ – James Schinner Aug 27 '17 at 13:27
  • \$\begingroup\$ @Mathias Ettinger - Thanks for the code btw, truly nice :) \$\endgroup\$ – James Schinner Aug 27 '17 at 13:32
4
\$\begingroup\$

I am not sure how critical this use case is, but when any of your arguments have a dictionary value, you have that dictionary items used as keyword arguments for the function:

In [1]: @return_kwargs
   ...: def example(arg1):
   ...:     pass
   ...: 

In [2]: arg1_value = {'arg3': 'value'}

In [3]: print(example(arg1_value))
{'arg3': 'value'}

In other words, arg3 here is printed as a keyword argument for the function but it was not actually unpacked into keyword arguments to the function at all - it was "sitting" inside the value of an arg1 positional argument.

\$\endgroup\$
  • \$\begingroup\$ True, It does, that is an interesting point. I hadn't thought of it in that way. In my mind, and use case. Declaring positional arguments on the function definition. Is placing a requirement to pass it. likeself on a method. \$\endgroup\$ – James Schinner Aug 27 '17 at 13:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.