Как "и" и "или" действуют с небулевыми значениями?
Я пытаюсь изучить 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
Вернуть первое значение Falsy, если оно есть, иначе вернуть последнее значение в выражении.
or
Верните первое значение Truthy, если оно есть, иначе верните последнее значение в выражении.
Поведение также суммировано в документах, особенно в этой таблице:
![enter image description here]()
Единственным оператором, возвращающим логическое значение независимо от его операндов, является оператор not
.
Оценки "Истина" и "Истина"
Заявление
len(args) and max(args) - min(args)
Это очень питонический лаконичный (и, возможно, менее читабельный) способ сказать "если args
не пуст, вернуть результат max(args) - min(args)
", в противном случае вернуть 0
. В общем, это более краткое представление выражения if-else
. Например,
exp1 and 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
. Обратите внимание, что если первое выражение оценивается как истинное, возвращаемое значение всегда является результатом второго выражения. Если первое выражение оценивается как ложное, то возвращаемый результат является результатом первого выражения.
В приведенной выше функции, если foo
получает один или несколько аргументов, len(args)
больше 0
(положительное число), поэтому возвращаемый результат равен max(args) - min(args)
. OTOH, если аргументы не переданы, len(args)
равно 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
. Если такие числа существуют, результатом понимания списка является непустой список, который является Истиной, поэтому он возвращается (короткое замыкание в действии здесь). Если таких чисел не существует, то результатом списка comp будет []
есть Falsy. Таким образом, второе выражение теперь оценивается (непустая строка) и возвращается.
Используя условные выражения, мы могли бы переписать эту функцию как
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 no need to check 3 at all
То же самое происходит и для or
, то есть оно вернет выражение, которое является Truthy, как только оно его обнаружит, потому что вычисление остальной части выражения является избыточным.
Вместо того, чтобы возвращать хардкор True
или False
, Python возвращает Truthy или Falsey, которые в любом случае будут оцениваться с помощью True
или False
. Вы можете использовать выражение как есть, и оно все равно будет работать.
Чтобы узнать, что Truthy и Falsey, проверьте ответ Патрика Хога
Ответ 3
и и или выполняют логическую логику, но при их сравнении они возвращают одно из фактических значений. При использовании и значения оцениваются в булевом контексте слева направо. 0, '', [],(), {}, и Нет являются ложными в булевом контексте; все остальное верно.
Если все значения истинны в булевом контексте, и возвращает последнее значение.
>>> 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)
, когда есть args else, он возвращает len(args)
, который равен 0.
Ответ 4
Является ли этот законный/надежный стиль, или есть какие-то ошибки?
Это законно, это оценка короткого замыкания, когда возвращается последнее значение.
Вы являетесь хорошим примером. Функция вернет 0
если аргументы не переданы, и код не должен проверять частный случай без аргументов.
Другим способом использования этого параметра является аргумент None по умолчанию для изменяемого примитива, например пустой список:
def fn(alist=None):
alist = alist or []
....
Если какое-либо значение, не относящееся к правде, передается, чтобы alist
умолчанию имеет пустой список, удобный способ избежать оператора if
и изменчивого аргумента аргумента по умолчанию
Ответ 5
Gotchas
Да, есть несколько gotchas.
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
Ответ 6
Да. Это правильное поведение и сравнение.
По крайней мере, в Python A and B
возвращает B
, если A
по существу True
, включая if A
NOT Null, NOT None
NOT пустой контейнер (например, пустой list
dict
и т.д.). Возвращается A
IFF A
по существу False
или None
или Empty или Null.
С другой стороны, A or B
возвращает A
, если A
по существу True
, включая if A
NOT Null, NOT None
NOT пустой контейнер (например, пустой 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"
Ответ 7
Является ли это законным/надежным стилем или есть какие-то ошибки на этом?
Я хотел бы добавить к этому вопросу, что он не только законный и надежный, но и ультра практичный. Вот простой пример:
>>>example_list = []
>>>print example_list or 'empty list'
empty list
Поэтому вы можете использовать его в своих интересах. Для того, чтобы быть таким, я вижу это:
Or
оператор
Оператор Python Or
возвращает первое значение Truth-y или последнее значение и останавливает
And
оператор
Оператор Python And
возвращает первое значение False-y или последнее значение и останавливает
За кулисами
В python все числа интерпретируются как True
за исключением 0. Поэтому, говоря:
0 and 10
совпадает с:
False and True
Это явно False
. Поэтому логично, что он возвращает 0
Ответ 8
понять по-простому,
И: 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, проверьте, верно ли 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
или => если кто-то ложь, это будет правдой. поэтому, если первое значение равно false, независимо от того, какое значение 2 предполагается. поэтому он возвращает второе значение, каким оно может быть.
если кто-то правдив, тогда это станет правдой. если оба являются ложными, то это станет ложным.