Skip to content Skip to sidebar Skip to footer

Dictionary With Some Mandatory Keys As Function Input

I have a function that has a dictionary as an argument. I will pass various dictionaries to it that have more entries than the few used inside the function. Additionally, I would l

Solution 1:

In python3.x, you can use function annotations:

>>>deffoo(indict: dict(apple=None, pear=None)):...print(indict)...>>>foo(dict())
{}

You can even go crazy with the now more widely accepted (by the interpreter) Ellipsis literal

>>>deffoo(indict: dict(apple=None, pear=None, extra_items=...)) -> int:...ifany(x notin indict for x in ('apple', 'pear')):...raise ValueError('message here...')...print(indict)...return3...>>>foo({})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in foo
ValueError: message here...
>>>foo({'apple':6, 'pear':4})
{'pear': 4, 'apple': 6}
3
>>>foo({'apple':6, 'pear':4, 'carrot':30000})
{'carrot': 30000, 'pear': 4, 'apple': 6}
3

As you can see from my first example, the annotation it doesn't enforce anything. You'd have to perform the validation in the function itself although I suppose you could introspect the required keys from the annotations if you wanted to keep it DRY, but it's probably not worth the effort for just 2 keys...

In python2.x (and more traditionally), perhaps you'd just want to put the information in the docstring ;-) -- And I'd recommend you do that for python3.x as well since that's the traditional place to go looking for documentation ...

UPDATE Note that now with fancy things like mypy sitting around, this answer is maybe a little outdated. You might consider annotating with a TypedDict from mypy_extensions. That should set expectations for your users and maybe even help catch some bugs if you use a type-checker like mypy.

from mypy_extensions import TypedDict

classApple:"""Represent an Apple."""classPear:"""Represent a Pear."""# "annotation-type" for a dictionary that has an apple and pear key whose values are Apple and Pear instances.
FruitBowl = TypedDict("FruitBowl": {"apple": Apple, "Pear": Pear})

deffoo(indict: FruitBowl) -> int:
    ...

Solution 2:

You could just check:

deffun(indict=dict(apple=None, pear=None)):
    if"apple"notin indict and"pear"notin indict:
        raise ValueError("'indict' must contain...")

However, you shouldn't really use a dictionary (or other mutable) default argument in Python; instead, prefer:

deffun(indict=None):
    if indict isNone:
        indict = {"apple": None, "pear": None}
    elif"apple"notin indict...

Or you could use update to ensure both keys are always present, rather than forcing the caller to provide them:

deffun(indict=None):
    defdict = {"apple": None, "pear": None}
    if indict isnotNone:
        defdict.update(indict)
    indict = defdict

Solution 3:

I see quite a few complicated answers for something that is really trivial:

defyourfunc(apple, pear, **kw):
   your_code_here

then at call time pass indict using the **kw syntax, ie:

indie = dict(pear=1, apple=2, whatever=42)
yourfunc(**indie)

No need to check anything, Python will do it by itself and raise the appropriate exception.

If you cannot change the call syntax, just wrap yourfunc with this simple decorator:

defkw2dict(func):
    defwrap(**kw):
        return func(kw)
    return wrap

(nb : should use functools to correctly wrap the decorator)

Solution 4:

One option would be to use keyword arguments and then dictionary expansion:

def fun(apple=None, pear=None, **unused_kwargs):
    # ... do stuff with apple and pear

and then when calling it...

fun(**arguments_dict)

This will automatically pull out the values for the "apple" and "pear" keys into variables, and leave everything else in a dictionary called unused_kwargs.


However, this still doesn't require the apple and pear keys to be present in and of itself - they'll just use the default values provided if left out. You could add checks for this:

deffun(apple=None, pear=None, **unused_kwargs):
    if apple isNoneor pear isNone:
        raise ValueError("Missing one or more required arguments.")

Solution 5:

Another option is to use a decorator:

@required(indict=('apple','pear'))
def fun(indict=None):
    print 'parameters are okay'

The somewhat complex decorator:

from functools import wraps

defrequired(**mandatory):
    defdecorator(f):
        @wraps(f)defwrapper(**dicts):
            for argname,d in dicts.items():
                for key in mandatory.get(argname,[]):
                    if key notin d:
                        raise Exception('Key "%s" is missing from argument "%s"' % (
                            key,argname))
            return f(**dicts)
        return wrapper
    return decorator

Examples:

>>> fun(indict={})
Traceback (most recent call last):
  ...
Exception: Key "apple"is missing from argument "indict"

>>> fun(indict={'apple':1})
Traceback (most recent call last):
  ...
Exception: Key "pear"is missing from argument "indict"

>>> fun(indict={'apple':1, 'pear':1})
parameters are okay

Post a Comment for "Dictionary With Some Mandatory Keys As Function Input"