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

问题描述 投票:2回答:8

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

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)):
python dictionary
8个回答
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

从我的第一个例子中可以看出,注释它没有强制执行任何操作。你必须在函数本身中执行验证,虽然我想如果你想保持DRY,你可以从annotations1内省所需的键,但是对于只有2个键来说可能不值得努力......

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

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

更新请注意,现在有像mypy这样的花哨的东西,这个答案可能有点过时了。您可以考虑使用TypedDict中的mypy_extensions进行注释。这应该为您的用户设定期望,如果您使用像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:
    ...

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

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

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正确包装装饰器)


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.")

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

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的每次调用都会出现这种情况)可能会导致不必要的开销。对你好吗?
  • 什么是您的应用程序回应这个“糟糕”数据的正确方法? (崩溃?)

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

0
投票

你可以使用precondition中的predefinedPythonDecoratorLibrarydecorator,而不是重新发明轮子:

# 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'
© www.soinside.com 2019 - 2024. All rights reserved.