Como “and” e “or” agem com valores não booleanos?

97

Estou tentando aprender python e encontrei um código que é bom e curto, mas não faz totalmente sentido

o contexto era:

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

Eu entendo o que está fazendo, mas por que o python faz isso - ou seja, retorna o valor em vez de Verdadeiro / Falso?

10 and 7-2

retorna 5. Da mesma forma, alterar e para ou resultará em uma alteração na funcionalidade. assim

10 or 7 - 2

Voltaria 10.

Este é um estilo legítimo / confiável ou há alguma pegadinha nisso?

Marcin
fonte
1
and(bem como or) não está restrito a trabalhar ou retornar valores booleanos.
cs95
1
IMNSHO: essa é uma maneira um tanto confusa de escrever isso; Não posso dizer de imediato se ele deve retornar um booleano (há um mínimo e máximo distintos) ou um número (qual é a diferença entre o mínimo e o máximo). No último caso, também há a questão de saber se faz algum sentido dar essa diferença de uma lista de comprimento zero como um número. (Em vez de Noneou uma exceção)
ilkkachu
7
Funciona, como outras pessoas explicaram, no entanto, um possível problema é que, se retornar, 0você não poderá dizer se argsestava vazio ou não, mas tinha todos os elementos iguais.
Especialmente Lime
@EspeciallyLime: exatamente. Eu mencionei isso na minha resposta .
Eric Duminil

Respostas:

135

TL; DR

Começamos resumindo os dois comportamentos dos dois operadores lógicos ande or. Esses idiomas formarão a base de nossa discussão a seguir.

and

Retorne o primeiro valor Falsy se houver algum; caso contrário, retorne o último valor na expressão.

or

Retorne o primeiro valor verdadeiro, se houver algum; caso contrário, retorne o último valor na expressão.

O comportamento também está resumido nos documentos , especialmente nesta tabela:

insira a descrição da imagem aqui

O único operador que retorna um valor booleano, independentemente de seus operandos, é o notoperador.


Avaliações de "veracidade" e "verdadeiras"

A declaração

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

É uma maneira concisa (e possivelmente menos legível) muito pythônica de dizer "se argsnão estiver vazio, retorne o resultado de max(args) - min(args)", caso contrário, retorne 0. Em geral, é uma representação mais concisa de uma if-elseexpressão. Por exemplo,

exp1 and exp2

Deve (aproximadamente) traduzir para:

r1 = exp1
if r1:
    r1 = exp2

Ou equivalente,

r1 = exp1 if exp1 else exp2

Similarmente,

exp1 or exp2

É equivalente a,

r1 = exp1
if not r1:
    r1 = exp2

Onde exp1e exp2são objetos Python arbitrários ou expressões que retornam algum objeto. A chave para entender os usos dos operadores lógicos ande oraqui é entender que eles não estão restritos a operar ou retornar valores booleanos. Qualquer objeto com um valor de veracidade pode ser testado aqui. Isto inclui int, str, list, dict, tuple, set, NoneType, e objectos utilizador definido. As regras de curto-circuito também se aplicam.

Mas o que é verdade?
Refere-se a como os objetos são avaliados quando usados ​​em expressões condicionais. @Patrick Haugh resume muito bem a verdade neste post .

Todos os valores são considerados "verdadeiros", exceto os seguintes, que são "falsos":

  • None
  • False
  • 0
  • 0.0
  • 0j
  • Decimal(0)
  • Fraction(0, 1)
  • [] - um vazio list
  • {} - um vazio dict
  • () - um vazio tuple
  • '' - um vazio str
  • b'' - um vazio bytes
  • set() - um vazio set
  • um vazio range , comorange(0)
  • objetos para os quais
    • obj.__bool__() retorna False
    • obj.__len__() retorna 0

Um valor "verdadeiro" irá satisfazer a verificação realizada por instruções ifou while. Usamos "verdadeiro" e "falso" para diferenciar dos boolvalores Truee False.


Quão and funciona

Nós construímos a pergunta do OP como uma segue para uma discussão sobre como esses operadores nesses casos.

Dada uma função com a definição

def foo(*args):
    ...

Como faço para retornar a diferença entre o valor mínimo e máximo em uma lista de zero ou mais argumentos?

Encontrar o mínimo e o máximo é fácil (use as funções integradas!). O único obstáculo aqui é lidar apropriadamente com o caso extremo em que a lista de argumentos pode estar vazia (por exemplo, chamar foo()). Podemos fazer ambos em uma única linha, graças ao andoperador:

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

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

foo()
# 0

Uma vez que andé usada, a segunda expressão também deve ser avaliada se a primeira for True. Observe que, se a primeira expressão for avaliada como verdadeira, o valor de retorno será sempre o resultado da segunda expressão . Se a primeira expressão for avaliada como Falsy, o resultado retornado será o resultado da primeira expressão.

Na função acima, If foorecebe um ou mais argumentos, len(args)é maior que 0(um número positivo), então o resultado retornado é max(args) - min(args). OTOH, se nenhum argumento é passado, len(args)é 0que é Falsas e0 é devolvido.

Observe que uma maneira alternativa de escrever esta função seria:

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

    return max(args) - min(args)

Ou, mais concisamente,

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

Claro, nenhuma dessas funções executa qualquer verificação de tipo, portanto, a menos que você confie completamente na entrada fornecida, não confie na simplicidade dessas construções.


Quão or funciona

Eu explico o funcionamento de oruma maneira semelhante com um exemplo inventado.

Dada uma função com a definição

def foo(*args):
    ...

Como você completaria foopara retornar todos os números 9000?

Usamos orpara lidar com o caso de canto aqui. Nós definimos foocomo:

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!'

fooexecuta uma filtragem na lista para reter todos os números 9000. Se existir algum desses números, o resultado da compreensão da lista é uma lista não vazia que é verdadeira, então ela é retornada (curto-circuito em ação aqui). Se não existirem tais números, então o resultado da lista comp []é Falsy. Portanto, a segunda expressão agora é avaliada (uma string não vazia) e é retornada.

Usando condicionais, poderíamos reescrever esta função como,

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

    return r

Como antes, essa estrutura é mais flexível em termos de tratamento de erros.

cs95
fonte
33
Não é "pítônico" sacrificar toda a clareza pela brevidade, o que penso ser o caso aqui. Não é uma construção simples.
DBedrenko
11
Acho que devemos observar que as expressões condicionais do Python tornaram essa sintaxe menos comum. Eu certamente prefiro max (args) - min (args) if len (args) else 0 ao original.
richardb
3
Outro comum que é confuso no início é atribuir um valor se nenhum existir: "some_var = arg or 3"
Erik
12
@Baldrickk Antes que as pessoas comecem a criticar essa sintaxe em favor dos operadores ternários, tenha em mente que, quando se trata de expressões de condição n-ária, os operadores ternários podem sair do controle rapidamente. Por exemplo, if ... else (if ... else (if ... else (if ... else ...)))pode muito bem ser reescrito ... and ... and ... and ... and ...e, nesse ponto, realmente se torna difícil argumentar a legibilidade para qualquer um dos casos.
cs95
4
Não é pitônico sacrificar a clareza pela brevidade, mas isso não acontece. É um idioma bem conhecido. É um idioma que você precisa aprender, como qualquer outro idioma, mas dificilmente 'sacrifica a clareza'.
Viagem de milhas
18

Citando do Python Docs

Observe que andnem or restringe o valor e o tipo para o qual eles retornam Falsee True, mas sim o último argumento avaliado . Isso às vezes é útil, por exemplo, se sfor uma string que deve ser substituída por um valor padrão se estiver vazia, a expressão s or 'foo'produz o valor desejado.

Então, é assim que o Python foi projetado para avaliar as expressões booleanas e a documentação acima nos dá uma ideia de por que eles fizeram isso.

Para obter um valor booleano, apenas faça um typecast.

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

Por quê?

Curto-circuito.

Por exemplo:

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

O mesmo vale para ortambém, ou seja, ele retornará a expressão que é Verdadeira assim que a encontrar, pois a avaliação do resto da expressão é redundante.

Em vez de retornar hardcore Trueou False, Python retorna Truthy ou Falsey , que de qualquer forma serão avaliados em Trueou False. Você pode usar a expressão como está e ela ainda funcionará.


Para saber o que é Truthy e Falsey , verifique a resposta de Patrick Haugh

Amit Joki
fonte
7

e e / ou executam lógica booleana, mas retornam um dos valores reais quando estão comparando. Ao usar e , os valores são avaliados em um contexto booleano da esquerda para a direita. 0, '', [], (), {} e None são falsos em um contexto booleano; tudo o mais é verdade.

Se todos os valores são verdadeiros em um contexto booleano, e retorna o último valor.

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

Se algum valor for falso em um contexto booleano e retornar o primeiro valor falso.

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

Então, o código

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

retorna o valor de max(args)-min(args)quando há args, senão ele retorna, len(args)que é 0.

Nithin Varghese
fonte
5

Este é um estilo legítimo / confiável ou há alguma pegadinha nisso?

Isso é legítimo, é uma avaliação de curto-circuito em que o último valor é retornado.

Você fornece um bom exemplo. A função retornará 0se nenhum argumento for passado, e o código não precisa verificar um caso especial de nenhum argumento passado.

Outra maneira de usar isso é definir como padrão os argumentos Nenhum para um primitivo mutável, como uma lista vazia:

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

Se algum valor não verdadeiro for passado para alistele, o padrão é uma lista vazia, uma maneira útil de evitar uma ifinstrução e a armadilha do argumento padrão mutável

Salparadise
fonte
3

Pegadinhas

Sim, existem algumas pegadinhas.

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

Primeiro, se fnretorna 0, você não pode saber se foi chamado sem nenhum parâmetro, com um parâmetro ou com vários parâmetros iguais:

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

O que isso fnsignifica?

Então, Python é uma linguagem dinâmica. Não é especificado em lugar nenhum o que fnfaz, como deve ser sua entrada e como deve ser sua saída. Portanto, é muito importante nomear a função corretamente. Da mesma forma, os argumentos não precisam ser chamados args. delta(*numbers)ou calculate_range(*numbers)pode descrever melhor o que a função deve fazer.

Erros de argumento

Finalmente, o andoperador lógico deve evitar que a função falhe se chamada sem nenhum argumento. Ainda assim, ele falha se algum argumento não for um número:

>>> 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'

Possível alternativa

Esta é uma maneira de escrever a função de acordo com "Mais fácil pedir perdão do que permissão". princípio :

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

Como um exemplo:

>>> 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

Se você realmente não deseja lançar uma exceção quando deltaé chamado sem nenhum argumento, você pode retornar algum valor que não seria possível de outra forma (por exemplo, -1ou 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
Eric Duminil
fonte
0

Este é um estilo legítimo / confiável ou há alguma pegadinha nisso?

Gostaria de acrescentar a esta questão que não é apenas legítimo e confiável, mas também ultra prático. Aqui está um exemplo simples:

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

Portanto, você pode realmente usá-lo a seu favor. Para estar consciente é assim que eu vejo:

Or operador

O oroperador do Python retorna o primeiro valor Truth-y, ou o último valor, e para

And operador

O andoperador do Python retorna o primeiro valor False-y, ou o último valor, e para

Por trás das cenas

Em python, todos os números são interpretados como Trueexceto 0. Portanto, dizendo:

0 and 10 

é o mesmo que:

False and True

O que é claramente False. Portanto, é lógico que ele retorne 0

Scharette
fonte
0

Sim. Este é o comportamento correto de comparação.

Pelo menos em Python, A and Bretornos Bse Aé essencialmente Trueincluindo, se Anão for nula, NÃO NoneNÃO um recipiente vazio (tais como um vazio list, dict, etc.). Aé retornado IFF Aé essencialmente FalseouNone ou Vazio ou Nulo.

Por outro lado, A or Bos retornos Ase Aé essencialmente Trueincluindo, se Anão for nula, NÃO NoneNÃO um recipiente vazio (tais como um vazio list, dict, etc.), caso contrário retorna B.

É fácil não notar (ou ignorar) esse comportamento porque, em Python, qualquer non-nullobjeto não vazio avaliado como True é tratado como um booleano.

Por exemplo, todos os itens a seguir imprimirão "Verdadeiro"

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"

Por outro lado, todos os itens a seguir imprimirão "Falso"

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

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

if set ([]): 
    print "True"
else: 
    print "False"
emmanuelsa
fonte
Obrigado. Eu queria escrever Aé essencialmente True. Corrigido.
emmanuelsa
0

entender de maneira simples,

E: if first_val is False return first_val else second_value

por exemplo:

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

mas,

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

e => se alguém for falso, será falso. se ambos forem verdadeiros, então só se tornará verdade

OU: if first_val is False return second_val else first_value

A razão é, se primeiro for falso, ele verifica se 2 é verdadeiro ou não.

por exemplo:

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

mas,

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

ou => se alguém for falso, será verdadeiro. portanto, se o primeiro valor for falso, não importa quais valores 2 suponham ser. portanto, ele retorna o segundo valor, seja o que for.

se alguém for verdadeiro, então se tornará verdadeiro. se ambos forem falsos, então se tornará falso.

Mohideen bin Mohammed
fonte