Как "и" и "или" действуют с небулевыми значениями?
Я пытаюсь изучить 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
TL; DR
Мы начнем с обобщения поведения двух логических операторов and
и or
. Эти идиомы лягут в основу нашего обсуждения ниже.
and
Возвращает первое ложное значение, если таковое имеется, в противном случае возвращает последнее значение в выражении.
or
Возвращает первое истинное значение, если таковое имеется, в противном случае возвращает последнее значение в выражении.
Поведение также кратко описано в документации, особенно в этой таблице:
Операция | Результат |
---|---|
x or y | если x равно false, то y, иначе x |
x and y | если x равно false, то x, иначе y |
not x | если x равно false, то True , else False |
Единственный оператор, возвращающий логическое значение независимо от его операндов, - это not
operator .
Оценки "правдивости" и "Правдивости"
Инструкция
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
Should (roughly) translate to:
r1 = exp1
if not r1:
r1 = exp2
Or, equivalently,
r1 = exp1 if exp1 else exp2
Where exp1
and exp2
are arbitrary python objects, or expressions that return some object. The key to understanding the uses of the logical and
and or
operators here is understanding that they are not restricted to operating on, or returning boolean values. Any object with a truthiness value can be tested here. This includes int
, str
, list
, dict
, tuple
, set
, NoneType
, and user defined objects. Short circuiting rules still apply as well.
But what is truthiness?
It refers to how objects are evaluated when used in conditional expressions. @Patrick Haugh summarises truthiness nicely in this post.
All values are considered "truthy" except for the following, which are
"falsy":
None
False
0
0.0
0j
Decimal(0)
Fraction(0, 1)
[]
- an emptylist
{}
- an emptydict
()
- an emptytuple
''
- an emptystr
b''
- an emptybytes
set()
- an emptyset
- an empty
range
, likerange(0)
- objects for which
obj.__bool__()
returnsFalse
obj.__len__()
returns0
A "truthy" value will satisfy the check performed by
if
orwhile
statements. We use "truthy" and "falsy" to differentiate from thebool
valuesTrue
andFalse
.
How and
Works
We build on OP's question as a segue into a discussion on how these operators in these instances.
Given a function with the definition
def foo(*args):
...How do I return the difference between the minimum and maximum value
in a list of zero or more arguments?
Найти минимум и максимум несложно (используйте встроенные функции!). Единственная загвоздка здесь заключается в правильной обработке углового случая, когда список аргументов может быть пустым (например, вызов 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)
. OTOH, если аргументы не переданы, len(args)
is 0
, который является ложным, и 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
Как и прежде, эта структура более гибкая с точки зрения обработки ошибок.
Ответ 2
Цитирую из документов по 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
too , то есть оно вернет правдивое выражение, как только найдет его, потому что вычисление остальной части выражения избыточно.
Вместо того, чтобы возвращать жесткое True
или False
, Python возвращает Truthy или Falsey, которые в любом случае будут оцениваться в True
или False
. Вы могли бы использовать выражение как есть, и оно все равно будет работать.
Чтобы узнать, что является правдивым, а что ложным, ознакомьтесь с ответом Патрика Хо
Ответ 3
и и или выполняют логическую логику, но при сравнении возвращают одно из фактических значений. При использовании и значения вычисляются в логическом контексте слева направо. 0, ", [], (), {}, и None являются false в логическом контексте; все остальное равно true.
Если все значения истинны в логическом контексте, и возвращает последнее значение.
>>> 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.
Ответ 4
Это законный / надежный стиль, или есть какие-то подводные камни по этому поводу?
Это законно, это вычисление короткого замыкания, при котором возвращается последнее значение.
Вы приводите хороший пример. Функция вернет 0
, если аргументы не переданы, и коду не нужно проверять наличие особого случая отсутствия переданных аргументов.
Другой способ использовать это - использовать аргументы None по умолчанию для изменяемого примитива, например, для пустого списка:
def fn(alist=None):
alist = alist or []
....
If some non-truthy value is passed to alist
it defaults to an empty list, handy way to avoid an if
statement and the mutable default argument pitfall