Как «и» и «или» действуют с небулевыми значениями?

101

Я пытаюсь изучить 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
IMNSHO: это несколько запутанный способ написания этого; Я не могу сразу сказать, должен ли он возвращать логическое значение (есть ли разные min и max) или число (в чем разница между min и max). Если последнее, то возникает также вопрос, имеет ли смысл указывать разницу в списке нулевой длины в виде числа. (Вместо Noneили исключения)
ilkkachu
7
Он работает, как объяснили другие люди, однако одна возможная проблема заключается в том, что если он возвращается, 0вы не можете определить, argsбыло ли оно пустым или непустым, но все элементы были равны.
Особенно Лайм
@EspecialLime: точно. Я упомянул об этом в своем ответе .
Эрик

Ответы:

139

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 = exp2 if exp1 else exp1

По аналогии,

exp1 or exp2

Должен (примерно) переводиться как:

r1 = exp1
if not r1:
    r1 = exp2

Или, что то же самое,

r1 = exp1 if exp1 else 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. Обратите внимание: если первое выражение оценивается как истинное, возвращаемое значение всегда является результатом второго выражения . Если первое выражение оценивается как ложное, то возвращаемый результат является результатом первого выражения.

В приведенной выше функции If 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. Если такие числа существуют, результатом понимания списка является непустой список, который является Истинным, поэтому он возвращается (здесь происходит короткое замыкание). Если таких чисел не существует, то результатом составления списка будет []Ложь. Итак, второе выражение теперь вычисляется (непустая строка) и возвращается.

Используя условные выражения, мы могли бы переписать эту функцию как,

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

Как и прежде, эта структура более гибкая с точки зрения обработки ошибок.

cs95
источник
33
Жертвовать всей ясностью ради краткости - не «питонический», как я думаю, здесь так и есть. Это непростая конструкция.
Д.Бедренко
11
Я думаю, следует отметить, что условные выражения Python сделали этот синтаксис менее распространенным. Я определенно предпочитаю max (args) - min (args) if len (args) else 0 оригиналу.
Ричардб
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

Обратите внимание , что ни 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и выражения, то есть он вернет выражение, которое является истинным, как только найдет его, потому что оценка остальной части выражения является избыточной.

Вместо того, чтобы возвращать hardcore Trueor False, Python возвращает Truthy или Falsey , которые в любом случае будут оценивать Trueor False. Вы можете использовать выражение как есть, и оно все равно будет работать.


Чтобы узнать, что такое Truthy and Falsey , проверьте ответ Патрика Хо

Амит Джоки
источник
7

и и / или выполняют логическую логику, но при сравнении они возвращают одно из фактических значений. При использовании и значения оцениваются в логическом контексте слева направо. 0, '', [], (), {} и None являются ложными в логическом контексте; все остальное правда.

Если все значения истинны в логическом контексте, и возвращает последнее значение.

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

Если какое-либо значение ложно в логическом контексте и возвращает первое ложное значение.

>>> '' 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оператора и ловушки изменяемого аргумента по умолчанию

Salparadise
источник
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Оператор Python возвращает первое значение False-y или последнее значение и останавливает

За кулисами

В python все числа интерпретируются как Trueкроме 0. Следовательно, говоря:

0 and 10 

такой же как:

False and True

Что ясно False. Поэтому логично, что он возвращает 0

Scharette
источник
0

Да. Это правильное поведение и сравнение.

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

С другой стороны, A or Bвозвращает Aif, Aпо существу, Trueвключая if 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

причина в том, что если первое ложно, то проверяется, истинно ли 2 или нет.

например:

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 значения предполагаются. поэтому он возвращает второе значение, каким бы оно ни было.

если кто-то прав, то это станет правдой. если оба ложны, тогда он станет ложным.

Мохидин бин Мохаммед
источник