我有一个以字典作为参数的函数。我将传递各种词典,其中包含的条目多于函数内部使用的几个。另外,我想在函数定义中看到需要哪些键。所以我写了
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)):
答案 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
等等......
首先应考虑的是您的应用程序以及您需要的速度性能。
尝试自己回答:
答案 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'