考虑:
def foobar(*, foo, bar):
if foo:
print('foo', end="")
if bar:
print('bar', end="")
if foo and bar:
print('No bueno', end='') # I want this to be impossible
if not foo and not bar:
print('No bueno', end='') # I want this to be impossible
print('')
foobar(foo='bar') # I want to pass inspection
foobar(bar='foo') # I want to pass inspection
foobar(foo='bar', bar='foo') # I want to fail inspection
foobar() # I want to fail inspection
是否有一种方法可以设置函数,以便仅在通过foo或bar时才通过检查,而无需手动检查函数内部?
答案 0 :(得分:4)
从语法上讲不。但是,使用装饰器执行此操作相对容易:
from functools import wraps
def mutually_exclusive(keyword, *keywords):
keywords = (keyword,)+keywords
def wrapper(func):
@wraps(func)
def inner(*args, **kwargs):
if sum(k in keywords for k in kwargs) != 1:
raise TypeError('You must specify exactly one of {}'.format(', '.join(keywords)))
return func(*args, **kwargs)
return inner
return wrapper
用作:
>>> @mutually_exclusive('foo', 'bar')
... def foobar(*, foo=None, bar=None):
... print(foo, bar)
...
>>> foobar(foo=1)
1 None
>>> foobar(bar=1)
None 1
>>> foobar(bar=1, foo=2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in inner
TypeError: You must specify exactly one of foo, bar
>>> foobar()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in inner
TypeError: You must specify exactly one of foo, bar
装饰器忽略给定列表中未包括的位置和关键字参数:
>>> @mutually_exclusive('foo', 'bar')
... def foobar(a,b,c, *, foo=None, bar=None, taz=None):
... print(a,b,c,foo,bar,taz)
...
>>> foobar(1,2,3, foo=4, taz=5)
1 2 3 4 None 5
>>> foobar(1,2,3, foo=4, bar=5,taz=6)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in inner
TypeError: You must specify exactly one of foo, bar
如果参数可能是“可选的”(即,您最多可以指定这些关键字参数之一,但也可以省略所有这些参数),只需将!= 1
更改为<= 1
或{{1 }}。
如果将in (0,1)
替换为数字1
,则将修饰器泛化为从提供的集合中准确(或最多)接受k
个指定参数。
但是无论如何这对PyCharm毫无帮助。据我目前所知,根本不可能告诉IDE您想要什么。
上面的修饰符有一个小“ bug”:它认为k
就像您传递了foo=None
的值一样,因为它出现在foo
列表中。通常,您希望传递默认值的行为与完全没有指定参数的行为相同。
要正确解决此问题,将需要检查kwargs
内部的func
以查找默认值,并使用类似wrapper
的方式更改k in keywords
。
答案 1 :(得分:0)
简而言之:不,您不能那样做。
与之最接近的可能是使用断言:
def foobar(foo=None, bar=None):
assert bool(foo) != bool(bar)
foobar(foo='bar') # Passes
foobar(bar='foo') # Passes
foobar(foo='bar', bar='foo') # Raises an AssertionError
foobar() # Raises an AssertionError
bool
转换和!=
的组合将构成逻辑XOR。
但是要小心断言;他们可以被禁用。如果仅在开发过程中需要检查,就可以了。
答案 2 :(得分:0)
标准库为此使用了简单的运行时检查:
def foobar(*, foo=None, bar=None):
if (foo is None) == (bar is None):
raise ValueError('Exactly one of `foo` and `bar` must be provided')
答案 3 :(得分:0)
您可以稍微进行重构,并采用两个 个非可选参数,这些参数共同提供一个值:
{0#}
,这是不可能的方式来传递两个FOO或酒吧的值。 PyCharm也会警告你,如果你添加了额外的参数。