Синтаксис Python для "якщо a або b або c, але не всі вони"


130

У мене є сценарій 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:
Edgar Aroutiounian

6
@EdgarAroutiounian len(sys.argv)завжди буде принаймні 1: він включає виконуваний файл як argv[0].
RoadieRich

10
Основна частина питання не відповідає заголовку питання. Ви хочете перевірити "якщо a чи b чи c, але не всі" чи "якщо саме один з a, b і c" (як це вислов, який ви дали)?
Дуг МакКлін

2
Що ви можете сказати про + b + c?
gukoff

6
Зачекайте, питання, це може приймати або нуль, або три аргументи. ви не могли просто сказати if not (a and b and c)(нульові аргументи), а потім if a and b and c(усі три арги)?
аколіт

Відповіді:


236

Якщо ви маєте на увазі мінімальну форму, перейдіть до цього:

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

Що перекладає назву вашого запитання.

ОНОВЛЕННЯ: як правильно сказано Волатильність та Супр, ви можете застосувати закон Де Моргана і отримати еквівалент:

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)
Нестабільність

208
Або навіть, if (a or b or c) and not (a and b and c)щоб ідеально відповідати титулу;)
Supr

3
@HennyH Я вважаю, що питання задає "принаймні одна умова, але не всі", а не "лише одна умова".
Нестабільність

63
@Suprif any([a,b,c]) and not all([a,b,c])
forevermatt

238

Як щодо:

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

Інший варіант:

if 1 <= sum(map(bool, conditions)) <= 2:
   ...

2
sum(conditions)може піти не так, якщо хтось із них повернеться, 2наприклад, що є True.
eumiro

7
Правда, вам знадобиться потворнеsum(map(bool, conditions))
jamylak

5
Зауважте, що це не коротке замикання, оскільки всі умови попередньо оцінюються.
georg

14
@PaulScheltema Перша форма зрозуміла для всіх.
смт

6
Цей "будь-який і не все" є найкращим і зрозумілішим з булевих методів, просто пам’ятайте про важливу відмінність між аргументом, який присутній, і аргументом, який є "неправдоподібним"
Вім

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

4
Так, я додав це навмисно. Можна було б зробити аргумент позиційним і встановити, що витрачається рівно 3 або 0, але це не було б хорошим CLI, тому я не рекомендував його.
Вім

8
Окреме питання. Ви не вірите, що це хороший CLI, і ви можете заперечити з цього приводу, і ОП може переконати. Але ваша відповідь значно відхиляється від питання, що потрібно зазначити зміну специфікації. Ви, здається, згинаєте специфікацію, щоб відповідати доступному інструменту, не згадуючи про зміни.
LarsH

2
@LarsH Добре, я додав приклад, який краще відповідає оригінальному інтерфейсу, який передбачає питання. Тепер він згинає інструмент для задоволення наявних специфікацій ...;)
wim

2
Це єдина відповідь, яку я підтримав. +1 для відповіді на реальне запитання .
Джонатан Райнхарт

1
+1. Форма CLI є важливим дотичним питанням, не повністю відокремленим, як сказала інша людина. Я підтримав вашу посаду так само, як і інші, - ваш стає в основі проблеми і пропонує елегантне рішення, тоді як інші повідомлення відповідають на буквальне питання. І обидва варіанти відповіді корисні і заслуговують +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)

заявляючи, що відповідь - це "мінімальна форма".


Насправді ця форма мінімальна. Це мінімальна форма PoS для виразу.
Стефано Санфіліппо

10

Це повертається, Trueякщо одна і лише одна з трьох умов True. Можливо, те, що ви хотіли у своєму прикладі коду.

if sum(1 for x in (a,b,c) if x) == 1:

Не така красива, як відповідь @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якщо у вас є між одним і двома справжніми твердженнями, і хибними, якщо всі помилкові або жодне неправдиве.

Це також спрощує, якщо ви використовуєте функції для оцінки булів, оскільки ви оцінюєте змінні лише один раз, а це означає, що ви можете записати функції вбудовано і не потрібно тимчасово зберігати змінні. (Приклад:. 0 < ( a(x) + b(x) + c(x) ) < 3)


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 не має нічого спільного з питанням, він додає ще одного імпорту, і якщо ОП взагалі не планує використовувати argparse, він не зможе допомогти їм чим раніше. Крім того, якщо "скрипт" не є самостійним, а модулем або функцією в більшому наборі коду, він може вже мати аналізатор аргументів, і ця конкретна функція в рамках цього більшого сценарію може бути за замовчуванням або налаштована. Через обмежену інформацію з ОП я не можу знати, як має діяти метод, але з впевненістю можна припустити, що ОП не проходить балів.
Inbar Rose

Питання чітко сказано: "У мене є скрипт python, який може приймати або нуль, або три аргументи командного рядка", він не сказав "У мене є функція, яка отримує 3 аргументи". Оскільки модуль argparse є кращим способом обробки аргументів командного рядка в python, він автоматично має все, що стосується питання. Нарешті, python - це "батареї включені" - немає жодних недоліків із "додаванням іншого імпорту", коли цей модуль є частиною стандартних бібліотек.
вім

@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 ...
всі вони рівні один одному!

Отже, нам просто потрібно знайти два елементи, які оцінюють різні bools,
щоб знати, що існує принаймні один 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)

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


-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оператори.


1
Я думаю, що це зайве ускладнення. Чому заморожений набір замість простого старого набору? Чому б .issupersetзамість того, щоб просто перевірити довжину 2, boolвсе одно не можна повернути нічого, крім True та False. Навіщо призначити лямбда (читати: анонімна функція) імені, а не просто використовувати def?
Вім

1
синтаксис лямбда для деяких більш логічний. вони однакові в будь-якому випадку довжина , так як вам потрібно , returnякщо ви використовуєте def. Я думаю, загальність цього рішення є приємною. не потрібно обмежувати себе булевими, по суті питання полягає в тому, "як я можу забезпечити, щоб усі ці елементи були в моєму списку". чому, setякщо вам не потрібна зміна? більше незмінність завжди краще, якщо вам не потрібна продуктивність.
Янус Троельсен

@JanusTroelsen Ви праві в цілі! Ось деякі причини, чому я зробив це так; це робить мені легше і зрозуміліше. Я схильний адаптувати Python до мого способу кодування :-).
Аббафей

але це не спрацює з нескінченними генераторами: P дивіться мою відповідь :) підказка:tee
Janus Troelsen

@JanusTroelsen Я розумію це :-). Спочатку я мав це навпаки (з True / False у наборі та ітерабельним у парамі методу) спочатку, але зрозумів, що це не працюватиме з нескінченними генераторами, і користувач може не усвідомлювати (оскільки цей факт не є (поки що) згадується в документах для ітерабельного набору параметрів методу), і принаймні так, очевидно, що він не буде приймати нескінченних ітераторів. Мені було відомо, itertools.teeале 1) Я шукав одноколірний матеріал, який був простим / малим, щоб гарантувати копіювання, 2) Ви вже дали відповідь, яка використовує цю техніку :-)
Аббафей
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.