¿Cómo actúan "y" y "o" con valores no booleanos?

99

Estoy tratando de aprender Python y encontré un código que es agradable y corto pero que no tiene mucho sentido.

el contexto fue:

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

Entiendo lo que está haciendo, pero ¿por qué Python hace esto, es decir, devuelve el valor en lugar de Verdadero / Falso?

10 and 7-2

devuelve 5. De manera similar, cambiar el y por o resultará en un cambio en la funcionalidad. Entonces

10 or 7 - 2

Volvería 10.

¿Es este estilo legítimo / confiable, o hay algún inconveniente en esto?

Marcin
fuente
1
and(así como or) no está restringido a trabajar o devolver valores booleanos.
cs95
1
En mi humilde opinión: esa es una forma algo confusa de escribir eso; No puedo decir a primera vista si se supone que debe devolver un valor booleano (hay un mínimo y un máximo distintos) o un número (cuál es la diferencia del mínimo y el máximo). Si es lo último, entonces también está la pregunta si tiene algún sentido dar esa diferencia de una lista de longitud cero como un número. (En lugar de Noneo una excepción)
ilkkachu
7
Funciona, como han explicado otras personas, sin embargo, un posible problema es que si regresa, 0no se puede saber si argsestaba vacío o no, pero tenía todos los elementos iguales.
Especialmente Lime
@EspeciallyLime: exactamente. Lo mencioné en mi respuesta .
Eric Duminil

Respuestas:

135

TL; DR

Comenzamos resumiendo los dos comportamientos de los dos operadores lógicos andy or. Estos modismos formarán la base de nuestra discusión a continuación.

and

Devuelve el primer valor de Falsy si hay alguno; de lo contrario, devuelve el último valor de la expresión.

or

Devuelve el primer valor de Verdad si hay alguno; de lo contrario, devuelve el último valor de la expresión.

El comportamiento también se resume en los documentos , especialmente en esta tabla:

ingrese la descripción de la imagen aquí

El único operador que devuelve un valor booleano independientemente de sus operandos es el notoperador.


Evaluaciones de "veracidad" y "veracidad"

La declaración

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

Es una forma muy pitónica concisa (y posiblemente menos legible) de decir "si argsno está vacío, devuelve el resultado de max(args) - min(args)"; de lo contrario, devuelve 0. En general, es una representación más concisa de una if-elseexpresión. Por ejemplo,

exp1 and exp2

Debería traducirse (aproximadamente) a:

r1 = exp1
if r1:
    r1 = exp2

O equivalente,

r1 = exp1 if exp1 else exp2

Similar,

exp1 or exp2

Es equivalente a,

r1 = exp1
if not r1:
    r1 = exp2

Donde exp1y exp2son objetos de python arbitrarios o expresiones que devuelven algún objeto. La clave para comprender los usos de los operadores lógicos andy oraquí es comprender que no están restringidos a operar o devolver valores booleanos. Aquí se puede probar cualquier objeto con un valor de veracidad. Esto incluye int, str, list, dict, tuple, set, NoneType, y el usuario define objetos. Las reglas de cortocircuito también se aplican.

Pero, ¿qué es la veracidad?
Se refiere a cómo se evalúan los objetos cuando se usan en expresiones condicionales. @Patrick Haugh resume muy bien la veracidad en esta publicación .

Todos los valores se consideran "verdaderos" excepto los siguientes, que son "falsos":

  • None
  • False
  • 0
  • 0.0
  • 0j
  • Decimal(0)
  • Fraction(0, 1)
  • [] - un vacío list
  • {} - un vacío dict
  • () - un vacío tuple
  • '' - un vacío str
  • b'' - un vacío bytes
  • set() - un vacío set
  • un vacío range, comorange(0)
  • objetos para los cuales
    • obj.__bool__() devoluciones False
    • obj.__len__() devoluciones 0

Un valor "veraz" satisfará la verificación realizada por ifo while declaraciones. Usamos "verdad" y "falsedad" para diferenciar de los boolvalores Truey False.


Como andfunciona

Nos basamos en la pregunta de OP como paso a una discusión sobre cómo funcionan estos operadores en estos casos.

Dada una función con la definición

def foo(*args):
    ...

¿Cómo devuelvo la diferencia entre el valor mínimo y máximo en una lista de cero o más argumentos?

Encontrar el mínimo y el máximo es fácil (¡use las funciones incorporadas!). El único inconveniente aquí es manejar adecuadamente el caso de la esquina donde la lista de argumentos podría estar vacía (por ejemplo, llamando foo()). Podemos hacer ambas cosas en una sola línea gracias al andoperador:

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

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

foo()
# 0

Dado que andse utiliza, la segunda expresión también debe evaluarse si la primera es True. Tenga en cuenta que, si la primera expresión se evalúa como veraz, el valor de retorno es siempre el resultado de la segunda expresión . Si la primera expresión se evalúa como falsa, el resultado devuelto es el resultado de la primera expresión.

En la función anterior, Si foorecibe uno o más argumentos, len(args)es mayor que 0(un número positivo), por lo que el resultado devuelto es max(args) - min(args). OTOH, si no se pasan argumentos, len(args)es 0cuál es Falsy y 0se devuelve.

Tenga en cuenta que una forma alternativa de escribir esta función sería:

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

    return max(args) - min(args)

O, de forma más concisa,

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

Si, por supuesto, ninguna de estas funciones realiza ninguna verificación de tipo, a menos que confíe completamente en la entrada proporcionada, no confíe en la simplicidad de estas construcciones.


Como orfunciona

Explico el funcionamiento de orde una manera similar con un ejemplo artificial.

Dada una función con la definición

def foo(*args):
    ...

¿Cómo completarías foopara devolver todos los números 9000?

Usamos orpara manejar el caso de la esquina aquí. 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!'

foorealiza una filtración en la lista para retener todos los números 9000. Si existen tales números, el resultado de la comprensión de la lista es una lista no vacía que es Verdad, por lo que se devuelve (cortocircuito en acción aquí). Si no existen tales números, entonces el resultado de la []composición de la lista es cuál es Falsy. Entonces, la segunda expresión ahora se evalúa (una cadena no vacía) y se devuelve.

Usando condicionales, podríamos reescribir esta función 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, esta estructura es más flexible en términos de manejo de errores.

cs95
fuente
33
No es "pitónico" sacrificar toda claridad por brevedad, que creo que es el caso aquí. No es una construcción sencilla.
DBedrenko
11
Creo que hay que tener en cuenta que las expresiones condicionales de Python han hecho que esta sintaxis sea menos común. Ciertamente prefiero max (args) - min (args) si len (args) más 0 que el original.
richardb
3
Otro común que es confuso al principio es asignar un valor si no existe ninguno: "some_var = arg or 3"
Erik
12
@Baldrickk antes de que la gente empiece a utilizar esta sintaxis en favor de los operadores ternarios, tenga en cuenta que cuando se trata de expresiones de condición n-aria, los operadores ternarios pueden salirse de control rápidamente. Por ejemplo, if ... else (if ... else (if ... else (if ... else ...)))también se puede reescribir como ... and ... and ... and ... and ...y en ese momento se vuelve realmente difícil argumentar la legibilidad en ambos casos.
cs95
4
No es pitónico sacrificar la claridad por la brevedad, pero esto no lo hace. Es un modismo muy conocido. Es un idioma que tienes que aprender, como cualquier otro idioma, pero difícilmente "sacrifica la claridad".
Miles Rout
18

Citando de Python Docs

Tenga en cuenta que ni andni or restringen el valor y el tipo al que regresan Falsey True, sino que devuelven el último argumento evaluado . Esto a veces es útil, por ejemplo, si ses una cadena que debe ser reemplazada por un valor predeterminado si está vacía, la expresión s or 'foo'produce el valor deseado.

Entonces, así es como se diseñó Python para evaluar las expresiones booleanas y la documentación anterior nos da una idea de por qué lo hicieron.

Para obtener un valor booleano, simplemente modifíquelo.

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

¿Por qué?

Cortocircuito.

Por ejemplo:

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

Lo mismo vale ortambién, es decir, devolverá la expresión que es Verdad tan pronto como la encuentre, ya que evaluar el resto de la expresión es redundante.

En lugar de devolver hardcore Trueo False, Python devuelve Truthy o Falsey , que de todos modos evaluarán Trueo False. Puede usar la expresión tal como está y seguirá funcionando.


Para saber qué es Truthy y Falsey , consulte la respuesta de Patrick Haugh

Amit Joki
fuente
7

y y o realizar la lógica booleana, pero devolver uno de los valores reales cuando están comparando. Cuando se usa y , los valores se evalúan en un contexto booleano de izquierda a derecha. 0, '', [], (), {} y None son falsos en un contexto booleano; todo lo demás es verdad.

Si todos los valores son verdaderos en un contexto booleano y devuelve el último valor.

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

Si algún valor es falso en un contexto booleano y devuelve el primer valor falso.

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

Entonces el código

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

devuelve el valor de max(args)-min(args)cuando hay argumentos, de lo contrario, devuelve len(args)que es 0.

Nithin Varghese
fuente
5

¿Es este estilo legítimo / confiable, o hay algún inconveniente en esto?

Esto es legítimo, es una evaluación de cortocircuito donde se devuelve el último valor.

Das un buen ejemplo. La función devolverá 0si no se pasan argumentos, y el código no tiene que buscar un caso especial de no pasar argumentos.

Otra forma de usar esto es asignar por defecto los argumentos None a una primitiva mutable, como una lista vacía:

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

Si se le pasa algún valor que no sea verdadero, el valor alistpredeterminado es una lista vacía, una forma práctica de evitar una ifdeclaración y la trampa del argumento predeterminado mutable

salparadise
fuente
3

Gotchas

Sí, hay algunas trampas.

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

Primero, si fnretorna 0, no puede saber si fue llamado sin ningún parámetro, con un parámetro o con múltiples parámetros iguales:

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

¿Qué fnsignifica?

Entonces, Python es un lenguaje dinámico. No se especifica en ninguna parte qué fnhace, cuál debería ser su entrada y cómo debería verse su salida. Por lo tanto, es muy importante nombrar la función correctamente. Del mismo modo, no es necesario invocar argumentos args. delta(*numbers)o calculate_range(*numbers)podría describir mejor lo que se supone que debe hacer la función.

Errores de argumento

Finalmente, andse supone que el operador lógico evita que la función falle si se llama sin ningún argumento. Sin embargo, todavía falla si algún argumento no es un 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'

Posible alternativa

Aquí hay una forma de escribir la función de acuerdo con "Es más fácil pedir perdón que permiso". principio :

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

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

Si realmente no desea generar una excepción cuando deltase llama sin ningún argumento, puede devolver algún valor que no puede ser posible de otra manera (por ejemplo, -1o 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
fuente
0

¿Es este estilo legítimo / confiable, o hay algún inconveniente en esto?

Me gustaría agregar a esta pregunta que no solo es legítimo y confiable, sino que también es ultra práctico. Aquí hay un ejemplo simple:

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

Por lo tanto, realmente puede usarlo a su favor. Para ser conciso, así es como lo veo:

Or operador

El oroperador de Python devuelve el primer valor Truth-y, o el último valor, y se detiene

And operador

El andoperador de Python devuelve el primer valor False-y, o el último valor, y se detiene

Entre bastidores

En Python, todos los números se interpretan como Trueexcepto 0. Por lo tanto, decir:

0 and 10 

es lo mismo que:

False and True

Que es claramente False. Por tanto, es lógico que devuelva 0

scharette
fuente
0

Si. Este es el comportamiento correcto y la comparación.

Al menos en Python, A and Brendimientos Bsi Ase esencialmente Trueincluyendo, si Ano es NULL, NO NoneNO un recipiente vacío (tal como un vacío list, dict, etc). Ase devuelve IFF Aes esencialmente Falseo Noneo Vacío o Nulo.

Por otro lado, A or Blos rendimientos Asi Ase esencialmente Trueincluyendo, si Ano es NULL, NO NoneNO un recipiente vacío (tal como un vacío list, dict, etc), de lo contrario devuelve B.

Es fácil no notar (o pasar por alto) este comportamiento porque, en Python, cualquier non-nullobjeto no vacío que se evalúe como True se trata como un booleano.

Por ejemplo, todo lo siguiente imprimirá "Verdadero"

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 otro lado, todo lo siguiente imprimirá "Falso"

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

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

if set ([]): 
    print "True"
else: 
    print "False"
emmanuelsa
fuente
Gracias. Quería escribir Aes esencialmente True. Corregido.
emmanuelsa
0

para entender de forma sencilla,

Y: if first_val is False return first_val else second_value

p.ej:

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

pero,

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

y => si alguien es falso, será falso. si ambos son ciertos, entonces solo se convertirá en cierto

O: if first_val is False return second_val else first_value

la razón es que, si primero es falso, compruebe si 2 es verdadero o no.

p.ej:

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

pero,

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

o => si alguien es falso, será cierto. entonces, si el primer valor es falso, no importa cuál se suponga que sea el valor 2. por lo que devuelve el segundo valor, lo que sea.

si alguien es verdad, se convertirá en verdad. si ambos son falsos, se volverá falso.

Mohideen bin Mohammed
fuente