Есть ли способ заставить параметры взаимоисключающих функций в Python?
Рассматривать:
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 ответа
Синтаксически нет. Однако это сделать относительно легко, используя декоратор:
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
или же in (0,1)
как ты предпочитаешь.
Если вы замените 1
с номером k
Вы обобщаете декоратор, чтобы принять точно (или самое большее) k
из указанных аргументов из предоставленного вами набора.
Это, однако, не поможет PyCharm в любом случае. Насколько я знаю в настоящее время просто невозможно сказать IDE, что вы хотите.
Вышеуказанный декоратор имеет небольшую "ошибку": он считает foo=None
как будто вы передали значение для foo
так как он появляется в kwargs
список. Обычно вы ожидаете, что передача значения по умолчанию должна вести себя идентично, как если бы вы вообще не указывали аргумент.
Чтобы исправить это правильно, потребуется проверить func
внутри wrapper
найти значения по умолчанию и изменить k in keywords
с чем-то вроде k in keywords and kwargs[k] != defaults[k]
,
Стандартная библиотека использует простую проверку времени выполнения для этого:
def foobar(*, foo=None, bar=None):
if (foo is None) == (bar is None):
raise ValueError('Exactly one of `foo` and `bar` must be provided')
Короче говоря: нет, вы не можете этого сделать.
Наиболее близким к этому может быть использование утверждения:
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.
Будьте осторожны с утверждениями, хотя; они могут быть отключены. Хорошо, если ваш чек требуется только во время разработки.
Вы можете немного изменить рефакторинг и взять два не необязательных параметра, которые вместе предоставляют одно значение:
def foobar(name, value):
if name == 'foo':
foo = value
elif name == 'bar':
bar = value
else:
raise ValueError()
Таким образом, невозможно передать два значения foo или bar. PyCharm также предупредит вас, если вы добавите дополнительные параметры.