Estoy tratando de aprender python y encontré un código que es agradable y corto pero que no tiene ningún 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 forma similar, cambiar el y para o dará lugar a un cambio en la funcionalidad. Asi que
10 or 7 - 2
Volvería 10.
¿Es este estilo legítimo / confiable, o hay algún error en esto?
Comenzamos por resumir los dos comportamientos de los dos operadores lógicos and
y or
. Estos modismos formarán la base de nuestra discusión a continuación.
and
Devuelve el primer valor de Falsey si hay alguno, de lo contrario, devuelve el último valor en la expresión.
or
Devuelve el primer valor de Verdad si hay alguno, de lo contrario, devuelve el último valor en la expresión.
El comportamiento también se resume en los documentos , especialmente en esta tabla:
El único operador que devuelve un valor booleano independientemente de sus operandos es el operador not
.
La statement
len(args) and max(args) - min(args)
Es una forma concisa muy pythonica (y posiblemente menos legible) de decir “si args
no está vacío, devuelva el resultado de max(args) - min(args)
“, de lo contrario, devuelva 0
. En general, es una representación más concisa de una expresión if-else
. Por ejemplo,
exp1 and exp2
Debería (a grandes rasgos) traducir a:
r1 = exp1 if not r1: r1 = exp2
O equivalente,
r1 = exp1 if exp1 else exp2
Donde exp1
y exp2
son objetos de python arbitrarios, o expresiones que devuelven algún objeto. La clave para entender los usos de los operadores lógicos and
/ or
aquí es entender que no están restringidos a operar o devolver valores booleanos. Cualquier objeto con un valor de verdad puede ser probado aquí. Esto incluye int
, str
, list
, dict
, tuple
, set
, NoneType
y objetos definidos por el usuario. Las reglas de cortocircuito también se aplican.
Pero ¿qué es la verdad?
Se refiere a cómo se evalúan los objetos cuando se usan en expresiones condicionales. @Patrick Haugh resume la veracidad muy bien en este post .
Todos los valores se consideran “veraces”, excepto los siguientes, que son “falsos”:
None
False
0
0.0
0j
Decimal(0)
Fraction(0, 1)
[]
– unalist
vacía{}
– undict
vacío()
– unatuple
vacía''
– unstr
vacíob''
– unbytes
vacíoset()
– unset
vacío- un
range
vacío, como elrange(0)
- objetos para los cuales
obj.__bool__()
devuelveFalse
obj.__len__()
devuelve0
Un valor “verdadero” satisfará la verificación realizada por las declaraciones
if
owhile
. Usamos “truthy” y “falsy” para diferenciarnos de los valoresbool
True
yFalse
.
and
funciona Nos basamos en la pregunta de OP como parte de una discusión sobre cómo 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!). La única pega aquí es manejar adecuadamente el caso de la esquina donde la lista de argumentos podría estar vacía (por ejemplo, llamando a foo()
). Podemos hacer ambas cosas en una sola línea gracias al operador and
:
def foo(*args): return len(args) and max(args) - min(args)
foo(1, 2, 3, 4, 5) # 4 foo() # 0
Dado que and
se 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 para ser veraz, el valor de retorno siempre es el resultado de la segunda expresión . Si la primera expresión se evalúa como Falsey, entonces el resultado devuelto es el resultado de la primera expresión.
En la función anterior, si foo
recibe 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 0
que es Falsey, y se devuelve 0
.
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, más concisamente,
def foo(*args): return 0 if not args else max(args) - min(args)
Por supuesto, ninguna de estas funciones realiza una comprobación de tipo, por lo que, a menos que confíe completamente en la entrada provista, no confíe en la simplicidad de estas construcciones.
or
funciona Explico el funcionamiento de or
de una manera similar con un ejemplo artificial.
Dada una función con la definición.
def foo(*args): ...
¿Cómo completarías
foo
para devolver todos los números de más de9000
?
Usamos or
para manejar el caso de la esquina aquí. Definimos foo
como:
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
realiza una filtración en la lista para retener todos los números de más de 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 lista comp es []
que es Falsey. Así que 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.
Citar de documentos de Python
Tenga en cuenta que ni
and
nior
restringen el valor y el tipo que devuelven aFalse
yTrue
, sino que devuelven el último argumento evaluado . Esto a veces es útil, por ejemplo, sis
es una cadena que debe reemplazarse por un valor predeterminado si está vacía, la expresións or 'foo'
produce el valor deseado.
Entonces, así es como Python fue diseñado para evaluar las expresiones booleanas y la documentación anterior nos da una idea de por qué lo hicieron así.
Para obtener un valor booleano simplemente encuéntralo.
return bool(len(args) and max(args)-min(args))
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 para or
también, es decir, devolverá la expresión que es Verdadero tan pronto como la encuentre, ya que evaluar el rest de la expresión es redundante.
En lugar de devolver el hardcore True
o False
, Python devuelve Truthy o Falsey , que de todos modos se van a evaluar como True
o False
. Podría usar la expresión tal como está, y seguirá funcionando.
Para saber qué es Truthy y Falsey , verifique la respuesta de Patrick Haugh
y y o ejecutan la lógica booleana, pero devuelven uno de los valores reales cuando se comparan. Al usar 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 args; de lo contrario, devuelve len(args)
que es 0.
¿Es este estilo legítimo / confiable, o hay algún error en esto?
Esto es legítimo, es una evaluación de cortocircuito donde se devuelve el último valor.
Usted proporciona un buen ejemplo. La función devolverá 0
si no se pasan los argumentos, y el código no tiene que verificar un caso especial de no pasar los argumentos.
Otra forma de usar esto, es predeterminar los argumentos de None a una primitiva mutable, como una lista vacía:
def fn(alist=None): alist = alist or [] ....
Si se pasa algún valor no verídico a una lista, se establece de manera predeterminada en una lista vacía, una forma práctica de evitar una sentencia if
y la trampa del argumento predeterminado mutable
Sí, hay algunas trampas.
fn() == fn(3) == fn(4, 4)
Primero, si fn
devuelve 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
fn
? Entonces, Python es un lenguaje dynamic. No se especifica en ningún lugar qué hace fn
, cuál debería ser su entrada y cómo debería ser su salida. Por lo tanto, es muy importante nombrar la función correctamente. Del mismo modo, los argumentos no tienen que ser llamados args
. delta(*numbers)
o calculate_range(*numbers)
podrían describir mejor lo que se supone que hace la función.
Finalmente, se supone que el operador lógico and
para evitar que la función falle si se llama sin ningún argumento. Aún así falla si algún argumento no es un número, sin embargo:
>>> fn('1') Traceback (most recent call last): File "", line 1, in File "", line 2, in fn TypeError: unsupported operand type(s) for -: 'str' and 'str' >>> fn(1, '2') Traceback (most recent call last): File " ", line 1, in File "", line 2, in fn TypeError: '>' not supported between instances of 'str' and 'int' >>> fn('a', 'b') Traceback (most recent call last): File " ", line 1, in File "", line 2, in fn TypeError: unsupported operand type(s) for -: 'str' and 'str'
Aquí hay una forma de escribir la función de acuerdo con la sección “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 "", line 1, in File "", 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 " ", line 1, in File "", line 5, in delta ValueError: delta should only be called with numerical arguments >>> delta('a', 'b') Traceback (most recent call last): File " ", line 1, in File "", line 5, in delta ValueError: delta should only be called with numerical arguments >>> delta('a', 3) Traceback (most recent call last): File " ", line 1, in File "", 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 se llama a delta
sin ningún argumento, podría devolver algún valor que de otra forma no sería posible (por ejemplo, -1
o 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
¿Es este estilo legítimo / confiable, o hay algún error en esto?
Me gustaría agregar a esta pregunta que no solo es legítima y confiable, sino que también es extremadamente práctica. Aquí hay un ejemplo simple:
>>>example_list = [] >>>print example_list or 'empty list' empty list
Por lo tanto, realmente puede utilizarlo en su ventaja. Para ser conciente así es como lo veo:
Or
operador
Python’s or
operator devuelve el primer valor de Verdad-y, o el último valor, y se detiene
And
operador
Python’s and
operador devuelven el primer valor False-y, o el último valor, y se detienen
Entre bastidores
En Python, todos los números se interpretan como True
excepción de 0. Por lo tanto, diciendo:
0 and 10
es lo mismo que:
False and True
Lo que es claramente False
. Por eso es lógico que devuelva 0
Sí. Este es el comportamiento correcto y la comparación.
Al menos en Python, A and B
devuelven B
si A
es esencialmente True
incluso si A
NO es Nulo, NO None
NO es un contenedor vacío (como una list
vacía, dict
, etc.). A
se devuelve IFF A
es esencialmente False
o None
o Vacío o Nulo.
Por otro lado, A or B
devuelve A
si A
es esencialmente True
incluso si A
NO es Nulo, NO None
NO es un contenedor vacío (como una list
vacía, dict
, etc.), de lo contrario, devuelve B
Es fácil no notar (o pasar por alto) este comportamiento porque, en Python, cualquier objeto que non-null
no vacío que se evalúe como Verdadero se trata como un valor 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"