python short-circuiting

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