Як “і” та “або” поводяться з небулевими значеннями?


99

Я намагаюся вивчити python і натрапив на якийсь приємний і короткий код, але зовсім не має сенсу

контекстом було:

def fn(*args):
    return len(args) and max(args)-min(args)

Я розумію, що він робить, але чому python робить це - тобто повертає значення, а не True / False?

10 and 7-2

повертає 5. Аналогічним чином, зміна і на або призведе до зміни функціональності. Так

10 or 7 - 2

Повернувся б 10.

Це законний / надійний стиль, чи є якісь проблеми в цьому?


1
and(як і or) не обмежується роботою або поверненням булевих значень.
cs95

1
ІМНШО: це дещо заплутаний спосіб написання цього; Я не можу сказати відразу, якщо він повинен повернути логічне значення (чи є різний min та max) або число (яка різниця min та max). Якщо останнє, тоді також виникає питання, чи є сенс надавати цю різницю списку нульової довжини як число. (Замість Noneабо виняток)
ilkkachu

7
Це працює, як пояснювали інші люди, однак одна з можливих проблем полягає в тому, що якщо вона повертається, 0ви не можете сказати, чи argsбуло порожнім чи не порожнім, але мали всі елементи рівними.
Особливо Лайм

@E SpecialLime: точно. Я згадав це у своїй відповіді .
Ерік Думініл

Відповіді:


135

TL; DR

Почнемо з підсумовування двох поведінок двох логічних операторів andі or. Ці ідіоми ляжуть в основу нашого обговорення нижче.

and

Поверніть перше значення Falsy, якщо воно є, інакше поверніть останнє значення у виразі.

or

Поверніть перше значення Truthy, якщо воно є, інакше поверніть останнє значення у виразі.

Поведінка також узагальнена в документах , особливо в цій таблиці:

введіть тут опис зображення

Єдиним оператором, що повертає булеве значення, незалежно від його операндів, є notоператор.


"Правдивість" та "Істинність" Оцінки

Заява

len(args) and max(args) - min(args)

Це дуже пітонічний лаконічний (і, можливо, менш читабельний) спосіб сказати "якщо argsне порожній, поверніть результат max(args) - min(args)", інакше поверніть 0. Загалом, це більш стисле подання if-elseвиразу. Наприклад,

exp1 and exp2

Чи слід (приблизно) перекласти на:

r1 = exp1
if r1:
    r1 = exp2

Або, що еквівалентно,

r1 = exp1 if exp1 else exp2

Так само,

exp1 or exp2

Еквівалентно,

r1 = exp1
if not r1:
    r1 = exp2

Де exp1і exp2є довільними об'єктами python, або виразами, що повертають якийсь об'єкт. Ключем до розуміння використання логіки andта orоператорів тут є розуміння того, що вони не обмежуються роботою або поверненням булевих значень. Тут можна перевірити будь-який об’єкт, що має значення істинності. Це включає в себе int, str, list, dict, tuple, set, NoneType, і певні користувачем об'єкти. Правила короткого замикання все ще застосовуються.

Але що таке правдивість?
Це стосується того, як обчислюються об’єкти при використанні в умовних виразах. @Patrick Haugh резюмує правдивість у цьому дописі .

Усі значення вважаються "неправдивими", за винятком наступних, які є "хибними":

  • None
  • False
  • 0
  • 0.0
  • 0j
  • Decimal(0)
  • Fraction(0, 1)
  • [] - порожній list
  • {} - порожній dict
  • () - порожній tuple
  • '' - порожній str
  • b'' - порожній bytes
  • set() - порожній set
  • порожній range, якrange(0)
  • об'єкти для яких
    • obj.__bool__() повертається False
    • obj.__len__() повертається 0

"Істинне" значення задовольнить перевірку, виконану ifабо while твердженнями. Ми використовуємо "неправдивий" та "хибний", щоб диференціювати boolзначення Trueта False.


Як andпрацює

Ми спираємося на питання OP як на півтону в дискусії про те, як працюють ці оператори в цих випадках.

Дано функцію з визначенням

def foo(*args):
    ...

Як повернути різницю між мінімальним і максимальним значенням у списку з нуля або більше аргументів?

Знайти мінімум і максимум просто (використовуйте вбудовані функції!). Єдина загроза тут полягає у правильній обробці кутового випадку, коли список аргументів може бути порожнім (наприклад, виклик foo()). Ми можемо зробити обидва в одному рядку завдяки andоператору:

def foo(*args):
     return len(args) and max(args) - min(args)

foo(1, 2, 3, 4, 5)
# 4

foo()
# 0

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

У наведеній вище функції, якщо fooотримує один або кілька аргументів, len(args)більше 0(позитивне число), тож результат повертається max(args) - min(args). Ото, якщо не передаються аргументи, len(args)в 0яких є Falsy, і 0повертається.

Зверніть увагу, що альтернативним способом написання цієї функції буде:

def foo(*args):
    if not len(args):
        return 0

    return max(args) - min(args)

Або, коротше,

def foo(*args):
    return 0 if not args else max(args) - min(args)

Звичайно, жодна з цих функцій не виконує жодної перевірки типу, тому, якщо ви повністю не довіряєте наданим введенням, не покладайтесь на простоту цих конструкцій.


Як orпрацює

Я пояснюю роботу " orподібним чином" на надуманому прикладі.

Дано функцію з визначенням

def foo(*args):
    ...

Як би ви завершили fooповернення всіх номерів 9000?

Тут ми orобробляємо кутовий футляр. Ми визначаємо fooяк:

def foo(*args):
     return [x for x in args if x > 9000] or 'No number over 9000!'

foo(9004, 1, 2, 500)
# [9004]

foo(1, 2, 3, 4)
# 'No number over 9000!'

fooвиконує фільтрацію в списку, щоб зберегти всі номери 9000. Якщо такі числа існують, результатом розуміння списку є непорожній список, який є істинним, тому він повертається (коротке замикання в дії тут). Якщо таких цифр не існує, то результатом списку comp є те, []що є Falsy. Отже, другий вираз обчислюється (не пустий рядок) і повертається.

Використовуючи умовні умови, ми могли б переписати цю функцію як,

def foo(*args):
    r = [x for x in args if x > 9000]
    if not r:
        return 'No number over 9000!' 

    return r

Як і раніше, ця структура є більш гнучкою з точки зору обробки помилок.


33
Не "пітонічно" жертвувати всією ясністю заради стислості, що, на мою думку, тут має місце. Це не проста конструкція.
Д.Б.Дедренко

11
Я думаю, слід зазначити, що умовні вирази Python зробили цей синтаксис менш поширеним. Я, звичайно, віддаю перевагу max (args) - min (args), якщо len (args) else 0 до оригіналу.
richardb

3
Ще одне загальне, що спочатку бентежить, - це присвоєння значення, якщо воно не існує: "some_var = arg or 3"
Ерік,

12
@Baldrickk, перш ніж люди почнуть обмінювати цей синтаксис на користь тернарних операторів, майте на увазі, що, коли справа стосується n-арних виразів умов, тернарні оператори можуть швидко вийти з-під контролю. Наприклад, if ... else (if ... else (if ... else (if ... else ...)))можна так само добре переписати, як ... and ... and ... and ... and ...і на той момент стає справді важко аргументувати читабельність для будь-якого випадку.
cs95

4
Не пітонічно жертвувати ясністю заради стислості, але це не робить цього. Це добре відома ідіома. Це ідіома, яку потрібно вивчити, як і будь-яка інша ідіома, але навряд чи вона «жертвує ясністю».
Miles Rout

18

Цитування з Python Docs

Зверніть увагу , що ні andні or не обмежують в вартості і типу вони повертаються Falseі True, а повертати останній оцінений аргумент . Це іноді корисно, наприклад, якщо sце рядок, який слід замінити значенням за замовчуванням, якщо воно порожнє, вираз s or 'foo'дає бажане значення.

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

Щоб отримати логічне значення, просто введіть його.

return bool(len(args) and max(args)-min(args))

Чому?

Коротке замикання.

Наприклад:

2 and 3 # Returns 3 because 2 is Truthy so it has to check 3 too
0 and 3 # Returns 0 because 0 is Falsey and there's no need to check 3 at all

Те саме стосується і того or, тобто він поверне вираз, який є істинним, як тільки знайде його, тому що обчислення решти виразу зайве.

Замість того, щоб повернути хардкор Trueабо False, Python повертає Truthy або Falsey , які в будь-якому випадку збираються оцінити Trueабо False. Ви можете використовувати вираз як є, і він все одно буде працювати.


Щоб знати, що таке Трюті та Фалсі , перевірте відповідь Патріка Хо


7

та і або виконують логічну логіку, але вони повертають одне з фактичних значень при порівнянні. При використанні та значення обчислюються у логічному контексті зліва направо. 0, '', [], (), {} і None є хибними у логічному контексті; все інше - правда.

Якщо всі значення істинні в логічному контексті, і повертає останнє значення.

>>> 2 and 5
5
>>> 2 and 5 and 10
10

Якщо якесь значення має значення false у логічному контексті і повертає перше значення false.

>>> '' and 5
''
>>> 2 and 0 and 5
0

Тож код

return len(args) and max(args)-min(args)

повертає значення, max(args)-min(args)коли є аргументи, інакше воно повертає, len(args)яке дорівнює 0.


5

Це законний / надійний стиль, чи є якісь проблеми в цьому?

Це правомірно, це оцінка короткого замикання, де повертається останнє значення.

Ви подаєте хороший приклад. Функція повернеться, 0якщо не передано жодного аргументу, і код не повинен перевіряти особливий випадок відсутності аргументів.

Інший спосіб використовувати це - встановити за замовчуванням None аргументи для змінного примітиву, наприклад, для порожнього списку:

def fn(alist=None):
    alist = alist or []
    ....

Якщо йому передається якесь неправдиве значення, alistвоно за замовчуванням порожній список, зручний спосіб уникнути ifтвердження та змінної помилки аргументу за замовчуванням


3

Маю

Так, є кілька помилок.

fn() == fn(3) == fn(4, 4)

По-перше, якщо fnповертається 0, ви не можете знати, чи було викликано без будь-якого параметра, з одним параметром або з кількома, рівними параметрами:

>>> fn()
0
>>> fn(3)
0
>>> fn(3, 3, 3)
0

Що це fnозначає?

Тоді Python - це динамічна мова. Ніде не вказано, що fnробить, яким має бути його вхід і як повинен виглядати його результат. Тому дуже важливо правильно назвати функцію. Подібним чином аргументи не потрібно називати args. delta(*numbers)або calculate_range(*numbers)може описати краще, що повинна виконувати функція.

Помилки аргументу

Нарешті, логічний andоператор повинен запобігти збою функції, якщо її викликати без аргументів. Це все одно не вдається, якщо якийсь аргумент не є числом, однак:

>>> fn('1')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in fn
TypeError: unsupported operand type(s) for -: 'str' and 'str'
>>> fn(1, '2')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in fn
TypeError: '>' not supported between instances of 'str' and 'int'
>>> fn('a', 'b')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in fn
TypeError: unsupported operand type(s) for -: 'str' and 'str'

Можлива альтернатива

Ось спосіб написати функцію відповідно до "Простіше просити прощення, ніж дозволу". принцип :

def delta(*numbers):
    try:
        return max(numbers) - min(numbers)
    except TypeError:
        raise ValueError("delta should only be called with numerical arguments") from None
    except ValueError:
        raise ValueError("delta should be called with at least one numerical argument") from None

Як приклад:

>>> delta()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in delta
ValueError: delta should be called with at least one numerical argument
>>> delta(3)
0
>>> delta('a')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in delta
ValueError: delta should only be called with numerical arguments
>>> delta('a', 'b')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in delta
ValueError: delta should only be called with numerical arguments
>>> delta('a', 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in delta
ValueError: delta should only be called with numerical arguments
>>> delta(3, 4.5)
1.5
>>> delta(3, 5, 7, 2)
5

Якщо ви дійсно не хочете викликати виняток, коли deltaвикликається без будь-якого аргументу, ви можете повернути якесь значення, яке не може бути можливим інакше (наприклад, -1або None):

>>> def delta(*numbers):
...     try:
...         return max(numbers) - min(numbers)
...     except TypeError:
...         raise ValueError("delta should only be called with numerical arguments") from None
...     except ValueError:
...         return -1 # or None
... 
>>> 
>>> delta()
-1

0

Це законний / надійний стиль, чи є якісь проблеми в цьому?

Я хотів би додати до цього питання, що воно не тільки законне та надійне, але й надзвичайно практичне. Ось простий приклад:

>>>example_list = []
>>>print example_list or 'empty list'
empty list

Тому ви дійсно можете використовувати його на свою користь. Для того, щоб бути лаконічним, я бачу це так:

Or оператора

orОператор Python повертає перше значення Truth-y або останнє значення і зупиняється

And оператора

Пітона andОператор повертає перше значення False-y або останнє значення і зупиняється

Поза сценою

У python всі числа інтерпретуються як Trueза винятком 0. Тому, кажучи:

0 and 10 

те саме, що:

False and True

Що однозначно False. Тому логічно, що він повертає 0


0

Так. Це правильна поведінка та порівняння.

Принаймні , в Python, A and Bповертається , Bякщо A, по суті , в Trueтому числі , якщо Aце НЕ Null, НЕ NoneНЕ порожній контейнер (наприклад, порожній list, dictі т.д.). Aповертається IFF Aпо суті FalseабоNone або Пустий, або Нульовий.

З іншого боку, A or Bповертається , Aякщо по Aсуті , в Trueтому числі , якщо Aце НЕ Null, НЕ NoneНЕ порожній контейнер (наприклад, порожній list, dictі т.д.), в іншому випадку вона повертає B.

Легко не помітити (або не помітити) таку поведінку, оскільки в Python будь non-null-який порожній об'єкт, що оцінює значення "True", трактується як логічний тип.

Наприклад, усе наступне буде друкувати "True"

if [102]: 
    print "True"
else: 
    print "False"

if "anything that is not empty or None": 
    print "True"
else: 
    print "False"

if {1, 2, 3}: 
    print "True"
else: 
    print "False"

З іншого боку, все наступне буде друкувати "False"

if []: 
    print "True"
else: 
    print "False"

if "": 
    print "True"
else: 
    print "False"

if set ([]): 
    print "True"
else: 
    print "False"

Дякую. Я хотів написати Aпо суті True. Виправлено.
emmanuelsa

0

зрозуміти просто,

І: if first_val is False return first_val else second_value

наприклад:

1 and 2 # here it will return 2 because 1 is not False

але,

0 and 2 # will return 0 because first value is 0 i.e False

і => якщо хтось хибний, це буде помилковим. якщо обидва істинні, тоді лише воно стане істинним

АБО: if first_val is False return second_val else first_value

причина полягає в тому, що якщо first - false, він перевіряє, чи є true і ні.

наприклад:

1 or 2 # here it will return 1 because 1 is not False

але,

0 or 2 # will return 2 because first value is 0 i.e False

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

якщо хтось правдивий, то це стане правдою. якщо обидва хибні, то це стане фальшивим.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.