带有一些强制键的字典作为函数输入

时间:2014-01-09 08:08:31

标签: python dictionary

我有一个以字典作为参数的函数。我将传递各种词典,其中包含的条目多于函数内部使用的几个。另外,我想在函数定义中看到需要哪些键。所以我写了

def fun(indict=dict(apple=None, pear=None)):

但是,该功能现在接受任何输入indict。是否有一种聪明的写作方式

any dictionary that has at least the keys 'apple' and 'pear' is accepted.

这样的东西
def fun(indict=dict(apple=NeedsToBeSpecified, pear=NeedsToBeSpecified)):

8 个答案:

答案 0 :(得分:5)

在python3.x中,您可以使用function annotations

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

你甚至可以对现在被广泛接受的(翻译)Ellipsis字面意思

发疯
>>> def foo(indict: dict(apple=None, pear=None, extra_items=...)) -> int:
...     if any(x not in indict for x in ('apple', 'pear')):
...         raise ValueError('message here...')
...     print(indict)
...     return 3
... 
>>> 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

从我的第一个示例中可以看出,它没有强制执行任何内容的注释。你必须在函数本身中执行验证,虽然我想你可以从注释 1 中内省所需的键,如果你想让它保持干燥,但它可能不值得为2而努力键...

在python2.x(以及更传统的)中,也许你只想把信息放在docstring中;-) - 我建议你为python3.x做那个,因为那是传统的地方去寻找文件...

1 keys = foo.__annotations__['indict'].keys() - {'extra_items'}

<强>更新 请注意,现在有像mypy这样的花哨的东西,这个答案可能有点过时了。您可以考虑使用mypy_extensions中的TypedDict进行注释。这应该为您的用户设定期望,如果您使用mypy之类的类型检查器,甚至可以帮助捕获一些错误。

from mypy_extensions import TypedDict

class Apple:
    """Represent an Apple."""

class Pear:
    """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})

def foo(indict: FruitBowl) -> int:
    ...

答案 1 :(得分:3)

您可以查看:

def fun(indict=dict(apple=None, pear=None)):
    if "apple" not in indict and "pear" not in indict:
        raise ValueError("'indict' must contain...")

但是,你不应该在Python中使用字典(或其他可变的)默认参数;相反,更喜欢:

def fun(indict=None):
    if indict is None:
        indict = {"apple": None, "pear": None}
    elif "apple" not in indict...

或者你可以使用update来确保两个键始终存在,而不是强制调用者提供它们:

def fun(indict=None):
    defdict = {"apple": None, "pear": None}
    if indict is  not None:
        defdict.update(indict)
    indict = defdict

答案 2 :(得分:2)

对于一些非常微不足道的事情,我看到了很多复杂的答案:

def yourfunc(apple, pear, **kw):
   your_code_here

然后在通话时使用indict语法传递**kw,即:

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

无需检查任何内容,Python将自行完成并引发相应的异常。

如果你不能改变调用语法,只需用这个简单的装饰器包裹yourfunc

def kw2dict(func):
    def wrap(**kw):
        return func(kw)
    return wrap

(nb:应使用functools正确包装装饰器)

答案 3 :(得分:1)

一种选择是使用关键字参数然后使用字典扩展:

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

然后在调用它时......

fun(**arguments_dict)

这会自动将“apple”和“pear”键的值拉出到变量中,并将其他所有内容保存在名为unused_kwargs的字典中。


然而,这仍然不需要苹果和梨钥匙本身 - 他们只会使用提供的默认值,如果省略。您可以为此添加检查:

def fun(apple=None, pear=None, **unused_kwargs):
    if apple is None or pear is None:
        raise ValueError("Missing one or more required arguments.")

答案 4 :(得分:1)

另一种选择是使用装饰器:

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

有点复杂的装饰者:

from functools import wraps

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

示例:

>>> 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

答案 5 :(得分:1)

请考虑Python中的“duck typing”主题(参见How to handle "duck typing" in Python?中的最佳答案)

通常有几种方法可以解决这类问题: 第一:indict中缺少密钥对您的代码是致命的:然后让它引发异常。

# Trust there are proper data in indict
def fun(indict):
    # do something with indict
    return indict["apple"]

fun({})
>> KeyError: 'apple'

第二:indict中缺少密钥并不致命:然后记录错误,执行解决方法/恢复所需的操作,然后继续。

# Try to recover from error
def fun(indict):
    if 'apple' not in indict:
        logging.warning('dict without apples in it.')
        return None
    return indict['apple']

fun({})
>> None

等等......

首先应考虑的是您的应用程序以及您需要的速度性能。

尝试自己回答:

  • 你为什么要这种检查?
  • 谁应该解决这个函数的问题用法(函数的所有者,函数的用户)?
  • 许多ifs,typechecks等(在你的func的每次调用中都存在)可能会导致不必要的开销。对你好吗?
  • 您的应用以什么方式回应这个“糟糕”的数据? (崩溃?)

答案 6 :(得分:0)

您可以添加check功能:

def fun(indict):
    check(indict, ('apple','pear'))
    print 'parameters are okay'

def check(d, keys):
    for k in keys:
        if not k in d:
            raise Exception("NeedsToBeSpecified: %s" % k)

如果dict中缺少必需的键,则会引发异常:

>>> fun({})
Traceback (most recent call last):
  ...
Exception: NeedsToBeSpecified: apple

>>> fun({'apple':1})
Traceback (most recent call last):
  ...
Exception: NeedsToBeSpecified: pear

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

答案 7 :(得分:0)

您可以在precondition中使用预定义的PythonDecoratorLibrary装饰器,而不是重新发明轮子:

# see https://wiki.python.org/moin/PythonDecoratorLibrary#Pre-.2FPost-Conditions
from pre_post_conditions import precondition

def indict_contains_keys(required):
    def checker(inval):
        assert isinstance(inval, dict)
        for key in required:
            if key not in inval:
                raise AssertionError('indict has no key %r' % key)
    return checker

@precondition(indict_contains_keys(['apple', 'pear']))
def fun(indict):
    print 'fun({!r} OK'.format(indict)

fun({'apple':1, 'pear': 2})           # fun({'pear': 2, 'apple': 1} OK
fun({'apple':1, 'pear': 2, 'fig': 3}) # fun({'pear': 2, 'apple': 1, 'fig': 3} OK
fun({'apple':1, 'fig': 3})            # AssertionError: indict has no key 'pear'