Синтаксис Python для выражения «если a, b или c, но не все»

131

У меня есть сценарий python, который может получать ноль или три аргумента командной строки. (Либо он работает по умолчанию, либо требует указания всех трех значений.)

Какой идеальный синтаксис для чего-то вроде:

if a and (not b or not c) or b and (not a or not c) or c and (not b or not a):

?

Крис Уилсон
источник
4
может начаться с чего-то вроде `if len (sys.argv) == 0:
Эдгар Арутюнян
6
@EdgarAroutiounian len(sys.argv)всегда будет не меньше 1: он включает исполняемый файл как argv[0].
RoadieRich
10
Текст вопроса не соответствует заголовку. Вы хотите проверить, «есть ли a, b или c, но не все», или «только одно из a, b и c» (как в приведенном вами выражении)?
Дуг МакКлин
2
Что вы можете сказать о a + b + c?
gukoff
6
Подождите, вопрос, он может принимать ноль или три аргумента. не могли бы вы просто сказать if not (a and b and c)(ноль аргументов), а затем if a and b and c(все три аргумента)?
послушник

Ответы:

237

Если вы имеете в виду минимальную форму, сделайте следующее:

if (not a or not b or not c) and (a or b or c):

Что переводит название вашего вопроса.

ОБНОВЛЕНИЕ: как правильно сказали Volatility и Supr, вы можете применить закон Де Моргана и получить эквивалент:

if (a or b or c) and not (a and b and c):

Я советую использовать ту форму, которая важнее для вас и других программистов. Первое означает «есть что-то ложное, но также и что-то истинное» , второе «Есть что-то истинное, но не все» . Если бы мне пришлось оптимизировать или сделать это аппаратно, я бы выбрал второй, здесь просто выберите наиболее читаемый (также принимая во внимание условия, которые вы будете тестировать, и их имена). Я выбрал первое.

Стефано Санфилиппо
источник
3
Все отличные ответы, но это выигрывает в плане краткости и большого сокращения. Спасибо всем!
Крис Уилсон
38
Я бы сделал его еще более кратким и остановился наif not (a and b and c) and (a or b or c)
Volatility
208
Или даже if (a or b or c) and not (a and b and c)идеально
подобрать
3
@HennyH Я считаю, что вопрос задает «хотя бы одно условие истинно, но не все», а не «только одно условие истинно».
Volatility
63
@Suprif any([a,b,c]) and not all([a,b,c])
eternalmatt
238

Как насчет:

conditions = [a, b, c]
if any(conditions) and not all(conditions):
   ...

Другой вариант:

if 1 <= sum(map(bool, conditions)) <= 2:
   ...
defuz
источник
2
sum(conditions)может пойти не так, если, 2например, возврат какой-либо из них True.
eumiro
7
Правда тебе бы некрасивыйsum(map(bool, conditions))
джамылак
5
Обратите внимание, что это не короткое замыкание, потому что все условия предварительно оцениваются.
georg
14
@PaulScheltema Первая форма понятнее для всех.
cmh
6
Это «любой, а не все» - лучший и самый ясный из логических методов, просто помните о важном различии между присутствующим аргументом и «истинным» аргументом
wim
115

На этот вопрос уже было много высоко оцененных ответов и принятый ответ, но все они до сих пор были отвлечены различными способами выражения логической проблемы и упустили важный момент:

У меня есть сценарий python, который может получать ноль или три аргумента командной строки. (Либо он работает по умолчанию, либо требует указания всех трех значений)

Эта логика не должна лежать в основе вашего кода , скорее, она должна обрабатыватьсяargparseмодулем. Не утруждайтесь написанием сложного оператора if, вместо этого лучше настроить парсер аргументов примерно так:

#!/usr/bin/env python
import argparse as ap
parser = ap.ArgumentParser()
parser.add_argument('--foo', nargs=3, default=['x', 'y', 'z'])
args = parser.parse_args()
print(args.foo)

И да, это должна быть опция, а не позиционный аргумент, потому что это, в конце концов, необязательно .


отредактировано: Чтобы решить проблему LarsH в комментариях, ниже приведен пример того, как вы могли бы написать это, если бы были уверены, что вам нужен интерфейс с 3 или 0 позиционными аргументами. Я считаю, что предыдущий интерфейс лучше по стилю, потому что необязательные аргументы должны быть параметрами , но вот альтернативный подход для полноты картины. Обратите внимание на переопределение kwargusageпри создании вашего парсера, потому что вargparseпротивном случае автоматически сгенерирует вводящее в заблуждение сообщение об использовании!

#!/usr/bin/env python
import argparse as ap
parser = ap.ArgumentParser(usage='%(prog)s [-h] [a b c]\n')
parser.add_argument('abc', nargs='*', help='specify 3 or 0 items', default=['x', 'y', 'z'])
args = parser.parse_args()
if len(args.abc) != 3:
  parser.error('expected 3 arguments')
print(args.abc)

Вот несколько примеров использования:

# default case
wim@wim-zenbook:/tmp$ ./three_or_none.py 
['x', 'y', 'z']

# explicit case
wim@wim-zenbook:/tmp$ ./three_or_none.py 1 2 3
['1', '2', '3']

# example failure mode
wim@wim-zenbook:/tmp$ ./three_or_none.py 1 2 
usage: three_or_none.py [-h] [a b c]
three_or_none.py: error: expected 3 arguments
Wim
источник
4
Да, я добавил это намеренно. Можно было бы сделать аргумент позиционным и обеспечить, чтобы потреблялось ровно 3 или 0, но это не будет хорошим CLI, поэтому я не рекомендовал его.
wim
8
Отдельный выпуск. Вы не верите, что это хороший интерфейс командной строки, и можете спорить с этим, и OP можно убедить. Но ваш ответ достаточно сильно отклоняется от вопроса, чтобы упомянуть об изменении спецификации. Кажется, вы меняете спецификацию, чтобы она соответствовала доступному инструменту, не говоря уже об изменении.
LarsH
2
@LarsH Хорошо, я добавил пример, который лучше соответствует исходному интерфейсу, подразумеваемому в вопросе. Теперь он изгибает инструмент, чтобы он соответствовал доступным спецификациям ...;)
wim
2
Это единственный ответ, за который я проголосовал. +1 за ответ на настоящий вопрос .
Джонатон Рейнхарт
1
+1. Форма интерфейса командной строки является важным второстепенным вопросом, а не полностью отдельным, как сказал другой человек. Я проголосовал за ваш пост, а также за другие сообщения - ваше раскрывает суть проблемы и предлагает элегантное решение, в то время как другие сообщения отвечают на буквальный вопрос. И оба типа ответов полезны и заслуживают +1.
Бен Ли
32

Я бы пошел на:

conds = iter([a, b, c])
if any(conds) and not any(conds):
    # okay...

Я думаю, это должно достаточно эффективно закоротить

объяснение

При создании condsитератора первое использование anyзакоротит и оставит итератор, указывающий на следующий элемент, если какой-либо элемент истинен; в противном случае он поглотит весь список и будет False. Следующий anyберет оставшиеся элементы в итерируемом объекте и проверяет, нет ли других истинных значений ... Если есть, то все утверждение не может быть истинным, поэтому нет ни одного уникального элемента (поэтому короткие замыкания очередной раз). Последний anyлибо вернет, Falseлибо исчерпает итерацию и будет True.

примечание: вышеупомянутое проверяет, установлено ли только одно условие


Если вы хотите проверить, установлен ли один или несколько элементов, но не все элементы, вы можете использовать:

not all(conds) and any(conds)
Джон Клементс
источник
5
Я не понимаю. Он читается так: если True, а не True. Помогите мне понять.
rGil
1
@rGil: он читается как «если некоторые яблоки красные, а некоторые нет» - это то же самое, что сказать «некоторые яблоки красные, но не все».
georg
2
Даже с объяснением я не могу понять поведение ... Разве [a, b, c] = [True, True, False]ваш код не должен «печатать» False, в то время как ожидаемый результат есть True?
awesoon
6
Это довольно умно, НО: я бы использовал этот подход, если бы вы не знали, сколько условий у вас было заранее, но для фиксированного, известного списка условий потеря читабельности просто не стоит того.
пушистый
4
Это не короткое замыкание. Список полностью создается перед передачей в iter. anyи allбудет лениво потреблять список, правда, но к тому времени, как вы туда доберетесь, список уже был полностью оценен!
icktoofay
22

Английское предложение:

"Если a, b или c, но не все из них"

Переводится в эту логику:

(a or b or c) and not (a and b and c)

Слово «но» обычно подразумевает союз, другими словами «и». Кроме того, «все они» переводит в сочетание условий: это условие, и это условие, и другое состояние. «Не» полностью инвертирует это соединение.

Я не согласен с принятым ответом. Автор не позаботился о применении наиболее простой интерпретации к спецификации и не применил закон Де Моргана, чтобы упростить выражение до меньшего числа операторов:

 not a or not b or not c  ->  not (a and b and c)

утверждая, что ответ - это «минимальная форма».

Kaz
источник
Собственно, эта форма минимальна. Это минимальная форма PoS для выражения.
Стефано Санфилиппо
10

Это возвращается, Trueесли выполнено одно и только одно из трех условий True. Вероятно, то, что вы хотели в своем примере кода.

if sum(1 for x in (a,b,c) if x) == 1:
eumiro
источник
Не так красиво, как ответ @defuz
jamylak
10

Что насчет: (уникальное состояние)

if (bool(a) + bool(b) + bool(c) == 1):

Обратите внимание: если вы разрешите два условия, вы можете это сделать

if (bool(a) + bool(b) + bool(c) in [1,2]):
Казимир и Ипполит
источник
1
Для протокола, вопрос требует двух условий. Хотя бы один, но не все = 1 или 2
Мариус Балчитис
ИМХО вы должны записать второй как 1 <= bool(a) + bool(b) + bool(c) <= 2.
Восстановить Монику
6

Чтобы было ясно, вы хотите принять свое решение на основе того, какая часть параметров имеет логическое значение ИСТИНА (в случае строковых аргументов - не пустые)?

argsne = (1 if a else 0) + (1 if b else 0) + (1 if c else 0)

Тогда вы приняли решение:

if ( 0 < argsne < 3 ):
 doSth() 

Теперь логика более понятна.

Дунайский моряк
источник
5

А почему бы просто не посчитать их?

import sys
a = sys.argv
if len(a) = 1 :  
    # No arguments were given, the program name count as one
elif len(a) = 4 :
    # Three arguments were given
else :
    # another amount of arguments was given
Луис
источник
5

Если вы не против быть немного загадочным, вы можете просто бросить вызов, 0 < (a + b + c) < 3который вернется, trueесли у вас есть от одного до двух истинных утверждений, и false, если все ложны или ни одно не является ложным.

Это также упрощается, если вы используете функции для оценки логических значений, поскольку вы оцениваете переменные только один раз, а это означает, что вы можете писать функции встроенными и не нужно временно хранить переменные. (Пример:. 0 < ( a(x) + b(x) + c(x) ) < 3)

Spinno
источник
4

В вопросе указано, что вам нужны либо все три аргумента (a и b и c), либо ни один из них (не (a или b или c))

Это дает:

(a и b и c) или нет (a или b или c)

Отдых на Кипре
источник
4

Насколько я понимаю, у вас есть функция, которая принимает 3 аргумента, но если этого не произойдет, она будет работать по умолчанию. Поскольку вы не объяснили, что должно произойти, когда предоставлены 1 или 2 аргумента, я предполагаю, что он должен просто выполнять поведение по умолчанию. В этом случае, я думаю, вам будет очень полезен следующий ответ:

def method(a=None, b=None, c=None):
    if all([a, b, c]):
        # received 3 arguments
    else:
        # default behavior

Однако, если вы хотите, чтобы 1 или 2 аргумента обрабатывались по-разному:

def method(a=None, b=None, c=None):
    args = [a, b, c]
    if all(args):
        # received 3 arguments
    elif not any(args):
        # default behavior
    else:
        # some args (raise exception?)

примечание: предполагается, что " False" значения не будут передаваться в этот метод.

Инбар Роуз
источник
проверка истинности аргумента отличается от проверки наличия или отсутствия аргумента
wim
@wim Так преобразует вопрос в соответствии с вашим ответом. Модуль argparse не имеет ничего общего с вопросом, он добавляет еще один импорт, и если OP вообще не планирует использовать argparse, он им ни в чем не поможет. Кроме того, если «сценарий» не является автономным, а является модулем или функцией в более крупном наборе кода, он может уже иметь синтаксический анализатор аргументов, и эта конкретная функция в этом более крупном сценарии может использоваться по умолчанию или настраиваться. Из-за ограниченной информации из OP я не могу знать, как должен действовать метод, но можно с уверенностью предположить, что OP не передает bools.
Инбар Роуз
Вопрос явно сказал: «У меня есть сценарий python, который может получать ноль или три аргумента командной строки», но не сказал «У меня есть функция, которая получает 3 аргумента». Поскольку модуль argparse является предпочтительным способом обработки аргументов командной строки в python, он автоматически имеет все, что связано с вопросом. Наконец, в python есть «батарейки» - нет никаких недостатков в «добавлении другого импорта», когда этот модуль является частью стандартных библиотек.
wim
@wim Вопрос непонятный (например, тело не соответствует заголовку). Я думаю, что вопрос достаточно неясен, поэтому это правильный ответ для некоторой его интерпретации.
Восстановить Монику
2

Если вы работаете с итератором условий, доступ к нему может быть медленным. Но вам не нужно обращаться к каждому элементу более одного раза, и вам не всегда нужно читать его полностью. Вот решение, которое будет работать с бесконечными генераторами:

#!/usr/bin/env python3
from random import randint
from itertools import tee

def generate_random():
    while True:
        yield bool(randint(0,1))

def any_but_not_all2(s): # elegant
    t1, t2 = tee(s)
    return False in t1 and True in t2 # could also use "not all(...) and any(...)"

def any_but_not_all(s): # simple
    hadFalse = False
    hadTrue = False
    for i in s:
        if i:
            hadTrue = True
        else:
            hadFalse = True
        if hadTrue and hadFalse:
            return True
    return False


r1, r2 = tee(generate_random())
assert any_but_not_all(r1)
assert any_but_not_all2(r2)

assert not any_but_not_all([True, True])
assert not any_but_not_all2([True, True])

assert not any_but_not_all([])
assert not any_but_not_all2([])

assert any_but_not_all([True, False])
assert any_but_not_all2([True, False])
Янус Троельсен
источник
0

Когда каждый данный boolесть True, или когда каждый данный boolесть False...
все они равны друг другу!

Таким образом, мы просто должны найти два элемента , которые оценивают в разные boolс ,
чтобы знать , что есть по крайней мере один , Trueи по крайней мере один False.

Мое короткое решение:

not bool(a)==bool(b)==bool(c)

Я верю, что это короткое замыкание, потому что AFAIK a==b==cравно a==b and b==c.

Мое обобщенное решение:

def _any_but_not_all(first, iterable): #doing dirty work
    bool_first=bool(first)
    for x in iterable:
        if bool(x) is not bool_first:
            return True
    return False

def any_but_not_all(arg, *args): #takes any amount of args convertable to bool
    return _any_but_not_all(arg, args)

def v_any_but_not_all(iterable): #takes iterable or iterator
    iterator=iter(iterable)
    return _any_but_not_all(next(iterator), iterator)

Я также написал код, связанный с несколькими итерациями, но удалил его отсюда, потому что считаю это бессмысленным. Однако он все еще доступен здесь .

GingerPlusPlus
источник
-2

Это в основном «некоторые (но не все)» функциональность (по контрасту с any()и all()встроенной функцией).

Это означает, что среди результатов должны быть Falses и True s. Таким образом, вы можете сделать следующее:

some = lambda ii: frozenset(bool(i) for i in ii).issuperset((True, False))

# one way to test this is...
test = lambda iterable: (any(iterable) and (not all(iterable))) # see also http://stackoverflow.com/a/16522290/541412

# Some test cases...
assert(some(()) == False)       # all() is true, and any() is false
assert(some((False,)) == False) # any() is false
assert(some((True,)) == False)  # any() and all() are true

assert(some((False,False)) == False)
assert(some((True,True)) == False)
assert(some((True,False)) == True)
assert(some((False,True)) == True)

Одним из преимуществ этого кода является то, что вам нужно выполнить итерацию только один раз по результирующим (логическим) элементам.

Одним из недостатков является то, что все эти выражения истинности всегда вычисляются и не производят короткого замыкания, как операторы or/ and.

Abbafei
источник
1
Я считаю, что это ненужное усложнение. Почему фризенсет вместо простого старого? Почему .issupersetвместо того, чтобы просто проверять длину 2, boolвсе равно не может вернуть ничего, кроме True и False. Зачем назначать лямбда (читай: анонимная функция) имени вместо простого использования def?
wim
1
лямбда-синтаксис для некоторых более логичен. в любом случае они одинаковой длины, так как вам нужно, returnесли вы используете def. я думаю, что универсальность этого решения хороша. нет необходимости ограничиваться логическими значениями, вопрос, по сути, заключается в том, «как мне убедиться, что все эти элементы присутствуют в моем списке». Зачемset если вам не нужна изменчивость? больше неизменяемости всегда лучше, если вам не нужна производительность.
Янус Трельсен
@JanusTroelsen Вы попали в цель! Вот несколько причин, по которым я поступил именно так; мне становится проще и понятнее. Я стараюсь адаптировать Python к своему способу программирования :-).
Аббафей
но он не будет работать на бесконечных генераторах: P см. мой ответ :) подсказка:tee
Janus Troelsen
@JanusTroelsen Я это понимаю :-). На самом деле у меня было наоборот (с True / False в наборе и итерацией в параметре метода) сначала, но я понял, что это не будет работать с бесконечными генераторами, и пользователь может не осознавать (поскольку этот факт не (пока) упомянутые в документации для параметров метода итеративного набора), и, по крайней мере, так очевидно, что он не будет принимать бесконечные итераторы. Мне было известно, itertools.teeно 1) я искал однострочник, который был бы простым / достаточно маленьким, чтобы гарантировать копирование-вставку, 2) вы уже дали ответ, который использует эту технику :-)
Abbafei