有没有办法在python中强制互斥函数参数?

时间:2019-02-01 20:38:04

标签: python python-3.x optional-parameters keyword-argument optional-arguments

考虑:

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时才通过检查,而无需手动检查函数内部?

4 个答案:

答案 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也会警告你,如果你添加了额外的参数。