Evaluación de cortocircuitos como "y" de Python mientras se almacenan los resultados de las comprobaciones
short-circuiting (14)
¡Hay muchas formas de hacer esto! Aquí está otro.
Puede utilizar una expresión generadora para diferir la ejecución de las funciones. Luego puede usar itertools.takewhile
para implementar la lógica de cortocircuito consumiendo elementos del generador hasta que uno de ellos sea falso.
from itertools import takewhile
functions = (check_a, check_b, check_c)
generator = (f() for f in functions)
results = tuple(takewhile(bool, generator))
if len(results) == len(functions):
return results
Tengo múltiples funciones caras que devuelven resultados. Quiero devolver una tupla de los resultados de todas las comprobaciones si todas las comprobaciones tienen éxito. Sin embargo, si una comprobación falla, no quiero llamar a las verificaciones posteriores, como el comportamiento de cortocircuito de and
. Podría anidar las declaraciones, pero eso se saldrá de control si hay muchos controles. ¿Cómo puedo obtener el comportamiento de cortocircuito and
al mismo tiempo, almacenar los resultados para su uso posterior?
def check_a():
# do something and return the result,
# for simplicity, just make it "A"
return "A"
def check_b():
# do something and return the result,
# for simplicity, just make it "B"
return "B"
...
Esto no hace cortocircuito:
a = check_a()
b = check_b()
c = check_c()
if a and b and c:
return a, b, c
Esto es complicado si hay muchos controles:
if a:
b = check_b()
if b:
c = check_c()
if c:
return a, b, c
¿Hay una manera más corta de hacer esto?
Como no puedo comentar "wim": s respuesta como invitado, solo agregaré una respuesta adicional. Como desea una tupla, debe recopilar los resultados en una lista y luego convertirlos en tupla.
def short_eval(*checks):
result = []
for check in checks:
checked = check()
if not checked:
break
result.append(checked)
return tuple(result)
# Example
wished = short_eval(check_a, check_b, check_c)
Cortocircuito flexible es mejor hacerlo con excepciones. Para un prototipo muy simple, incluso podría hacer valer cada resultado de verificación:
try:
a = check_a()
assert a
b = check_b()
assert b
c = check_c()
assert c
return a, b, c
except AssertionException as e:
return None
Probablemente debería plantear una excepción personalizada en su lugar. Puede cambiar las funciones de check_X para generar excepciones, de forma anidada y arbitraria. O puede envolver o decorar sus funciones check_X para generar errores en valores de retorno falsos.
En resumen, el manejo de excepciones es muy flexible y exactamente lo que está buscando, no tenga miedo de usarlo. Si aprendió en alguna parte que el manejo de excepciones no debe usarse para su propio control de flujo, esto no se aplica a python. El uso liberal del manejo de excepciones se considera pythonic, como en EAFP .
En otros idiomas que tenían asignaciones como expresiones que podría usar
if (a = check_a()) and (b = check_b()) and (c = check_c()):
Pero Python no es tal lenguaje. Aún así, podemos sortear la restricción y emular ese comportamiento:
result = []
def put(value):
result.append(value)
return value
if put(check_a()) and put(check_b()) and put(check_c()):
# if you need them as variables, you could do
# (a, b, c) = result
# but you just want
return tuple(result)
Esto podría aflojar demasiado la conexión entre las variables y la función, por lo que si desea hacer muchas cosas separadas con las variables, en lugar de usar los elementos de result
en el orden en que se colocaron en la lista, preferiría evitar Este enfoque. Aún así, podría ser más rápido y más corto que algunos bucles.
Escribe una función que tome una iterable de funciones para ejecutar. Llame a cada uno y agregue el resultado a una lista, o devuelva None
si el resultado es False
. O bien la función dejará de llamar a otras comprobaciones después de que una de ellas falle, o devolverá los resultados de todas las comprobaciones.
def all_or_none(checks, *args, **kwargs):
out = []
for check in checks:
rv = check(*args, **kwargs)
if not rv:
return None
out.append(rv)
return out
rv = all_or_none((check_a, check_b, check_c))
# rv is a list if all checks passed, otherwise None
if rv is not None:
return rv
def check_a(obj):
...
def check_b(obj):
...
# pass arguments to each check, useful for writing reusable checks
rv = all_or_none((check_a, check_b), obj=my_object)
Esto es similar a la respuesta de Bergi, pero creo que esa respuesta pierde el sentido de querer funciones separadas (check_a, check_b, check_c):
list1 = []
def check_a():
condition = True
a = 1
if (condition):
list1.append(a)
print ("checking a")
return True
else:
return False
def check_b():
condition = False
b = 2
if (condition):
list1.append(b)
print ("checking b")
return True
else:
return False
def check_c():
condition = True
c = 3
if (condition):
list1.append(c)
print ("checking c")
return True
else:
return False
if check_a() and check_b() and check_c():
# won''t get here
tuple1 = tuple(list1)
print (tuple1)
# output is:
# checking a
# (1,)
O, si no desea utilizar la lista global, pase una referencia de una lista local a cada una de las funciones.
Otra forma de abordar esto es mediante el uso de un generador, ya que los generadores utilizan la evaluación perezosa. Primero ponga todos los cheques en un generador:
def checks():
yield check_a()
yield check_b()
yield check_c()
Ahora puedes forzar la evaluación de todo convirtiéndolo en una lista:
list(checks())
Pero la función estándar de todas las funciones realiza una evaluación de atajo adecuada en el iterador devuelto por cheques () y devuelve si todos los elementos son veraces:
all(checks())
Por último, si desea que los resultados de las comprobaciones subsiguientes lleguen al error, puede usar itertools.takewhile para realizar la primera ejecución de valores de verdad solamente. Dado que el resultado de takewhile es perezoso, deberá convertirlo en una lista para ver el resultado en un REPL:
from itertools import takewhile
takewhile(lambda x: x, checks())
list(takewhile(lambda x: x, checks()))
Podría usar una lista o un OrderedDict, usar un bucle for serviría para emular cortocircuitos.
from collections import OrderedDict
def check_a():
return "A"
def check_b():
return "B"
def check_c():
return "C"
def check_d():
return False
def method1(*args):
results = []
for i, f in enumerate(args):
value = f()
results.append(value)
if not value:
return None
return results
def method2(*args):
results = OrderedDict()
for f in args:
results[f.__name__] = result = f()
if not result:
return None
return results
# Case 1, it should return check_a, check_b, check_c
for m in [method1, method2]:
print(m(check_a, check_b, check_c))
# Case 1, it should return None
for m in [method1, method2]:
print(m(check_a, check_b, check_d, check_c))
Prueba esto:
mapping = {''a'': assign_a(), ''b'': assign_b()}
if None not in mapping.values():
return mapping
Donde assign_a
y assign_b
son de la forma:
def assign_<variable>():
if condition:
return value
else:
return None
Puedes intentar usar el decorador lazy_python
package lazy_python
. Ejemplo de uso:
from lazy import lazy_function, strict
@lazy_function
def check(a, b):
strict(print(''Call: {} {}''.format(a, b)))
if a + b > a * b:
return ''{}, {}''.format(a, b)
a = check(-1, -2)
b = check(1, 2)
c = check(-1, 2)
print(''First condition'')
if c and a and b: print(''Ok: {}''.format((a, b)))
print(''Second condition'')
if c and b: print(''Ok: {}''.format((c, b)))
# Output:
# First condition
# Call: -1 2
# Call: -1 -2
# Second condition
# Call: 1 2
# Ok: (''-1, 2'', ''1, 2'')
Sólo tiene que utilizar un bucle de edad simple:
results = {}
for function in [check_a, check_b, ...]:
results[function.__name__] = result = function()
if not result:
break
Los resultados serán una asignación del nombre de la función a sus valores de retorno, y puede hacer lo que quiera con los valores después de que se rompa el bucle.
Use una cláusula else
en el bucle for si desea un manejo especial para el caso en el que todas las funciones hayan arrojado resultados verídicos.
Si la principal objeción es
Esto es complicado si hay muchos controles:
if a:
b = check_b()
if b:
c = check_c()
if c:
return a, b, c
Un patrón bastante agradable es revertir la condición y regresar temprano
if not a:
return # None, or some value, or however you want to handle this
b = check_b()
if not b:
return
c = check_c()
if not c:
return
# ok, they were all truthy
return a, b, c
Si no necesita tomar un número arbitrario de expresiones en tiempo de ejecución (posiblemente envuelto en lambdas), puede expandir su código directamente en este patrón:
def f ():
try:
return (<a> or jump(),
<b> or jump(),
<c> or jump())
except NonLocalExit:
return None
Donde se aplican esas definiciones:
class NonLocalExit(Exception):
pass
def jump():
raise NonLocalExit()
lógica principal:
results = list(takewhile(lambda x: x, map(lambda x: x(), function_list)))
if len(results) == len(function_list):
return results
puede aprender mucho sobre las transformaciones de la colección si observa todos los métodos de una API como http://www.scala-lang.org/api/2.11.7/#scala.collection.immutable.List y busque / implemente equivalentes de python
Lógica con configuración y alternativas:
import sys
if sys.version_info.major == 2:
from collections import imap
map = imap
def test(bool):
def inner():
print(bool)
return bool
return inner
def function_for_return():
function_list = [test(True),test(True),test(False),test(True)]
from itertools import takewhile
print("results:")
results = list(takewhile(lambda x:x,map(lambda x:x(),function_list)))
if len(results) == len(function_list):
return results
print(results)
#personally i prefer another syntax:
class Iterator(object):
def __init__(self,iterable):
self.iterator = iter(iterable)
def __next__(self):
return next(self.iterator)
def __iter__(self):
return self
def map(self,f):
return Iterator(map(f,self.iterator))
def takewhile(self,f):
return Iterator(takewhile(f,self.iterator))
print("results2:")
results2 = list(
Iterator(function_list)
.map(lambda x:x())
.takewhile(lambda x:x)
)
print(results2)
print("with additional information")
function_list2 = [(test(True),"a"),(test(True),"b"),(test(False),"c"),(test(True),"d")]
results3 = list(
Iterator(function_list2)
.map(lambda x:(x[0](),x[1]))
.takewhile(lambda x:x[0])
)
print(results3)
function_for_return()