¿Qué debo usar en lugar de la asignación en una expresión en Python?

De acuerdo con esta página no se puede usar código como

if variable = something(): #do something with variable, whose value is the result of something() and is true 

Así que en caso de que quiera tener la siguiente estructura de código:

 if a = something(): #do something with a elif a = somethingelse(): #... #5 more elifs 

donde las funciones something () son intensivas en cómputo (me refiero a que usar la función y luego hacerlo de nuevo para asignar valor a una variable en caso de que la primera sea verdadera), ¿qué debo escribir en Python? ¿Agregar 7 variables más en lugar de 1?

Tuve este problema hace años, en 2001, ya que estaba transliterando a Python desde un algoritmo de referencia en C que utilizaba fuertemente asignar y probar, tenía ganas de mantener una estructura similar para el primer borrador (luego refactorizarlo más tarde una vez la corrección fue bien probada). Así que escribí una receta en el Cookbook (ver también aquí ), que se reduce a …

 class DataHolder(object): def set(self, value): self.value = value; return value 

entonces el árbol if / elif puede convertirse en:

 dh = DataHolder() if dh.set(something()): # do something with dh.value elif dh.set(somethingelse()): # ... 

la clase DataHolder puede ser embellecida claramente de varias maneras (y está tan embellecida en las versiones en línea como en la de libro), pero esto es lo esencial, y es suficiente para responder a su pregunta.

Podrías hacer esto:

 a = something() if a: #do something with a else: a = somethingelse() if a: #... else: #5 more nested ifs 

O, dentro de una función, puede limitar el nivel de anidamiento con un return en cada caso coincidente:

 def f(): a = something() if a: #do something with a return a = somethingelse() if a: #... return #5 more ifs 

Otra alternativa que ofrece cierta flexibilidad:

 # Functions to be tested (can be expanded): tests = [something, somethingelse, yetsomethingelse, anotherfunction, another] for i, f in enumerate(tests): a = f() if a: if i == 0: # do something with a elif 1 <= i <= 3: # do something else with a else: # ... break 

O puedes compararlo explícitamente con la función:

 tests = [something, somethingelse, yetsomethingelse, anotherfunction, another] for i, f in enumerate(tests): a = f() if a: break if not a: # no result elif f == something: # ... elif f == somethingelse: # ... 

Si algunas de las funciones toman argumentos, puede usar lambda para mantener el paradigma de la función:

 tests = [lambda: something(args), somethingelse, lambda: something(otherargs)] for i, f in enumerate(tests): a = f() if a: break if not a: # no result elif i == 0: # ... elif i == 1: # ... 

Conviértete en un objeto llamable simple que guarda su valor devuelto:

 class ConditionValue(object): def __call__(self, x): self.value = x return bool(x) 

Ahora úsalo así:

 # example code makelower = lambda c : c.isalpha() and c.lower() add10 = lambda c : c.isdigit() and int(c) + 10 test = "ABC123.DEF456" val = ConditionValue() for t in test: if val(makelower(t)): print t, "is now lower case ->", val.value elif val(add10(t)): print t, "+10 ->", val.value else: print "unknown char", t 

Huellas dactilares:

 A is now lower case -> a B is now lower case -> b C is now lower case -> c 1 +10 -> 11 2 +10 -> 12 3 +10 -> 13 unknown char . D is now lower case -> d E is now lower case -> e F is now lower case -> f 4 +10 -> 14 5 +10 -> 15 6 +10 -> 16 

Puede que me esté faltando algo, pero ¿no podría factorizar cada una de las twigs en su statement if nivel superior en funciones separadas, crear una lista de tuplas de prueba para actuar y recorrerlas? Debería poder aplicar este patrón para imitar if (value=condition()) {} else if (value=other_condition()) {} estilo lógico.

Esta es realmente una extensión de la respuesta de redglyph y probablemente se puede comprimir a un iterador que genera StopIteration una vez que ha alcanzado un valor de verdad.

 # # These are the "tests" from your original if statements. No # changes should be necessary. # def something(): print('doing something()') # expensive stuff here def something_else(): print('doing something_else()') # expensive stuff here too... but this returns True for some reason return True def something_weird(): print('doing something_weird()') # other expensive stuff # # Factor each branch of your if statement into a separate function. # Each function will receive the output of the test if the test # was selected. # def something_action(value): print("doing something's action") def something_else_action(value): print("doing something_else's action") def something_weird_action(value): print("doing something_weird's action") # # A simple iteration function that takes tuples of (test,action). The # test is called. If it returns a truth value, then the value is passed # onto the associated action and the iteration is stopped. # def do_actions(*action_tuples): for (test,action) in action_tuples: value = test() if value: return action(value) # # ... and here is how you would use it: # result = do_actions( (something, something_action), (something_else, something_else_action), (something_weird, something_weird_action) ) 

Podría usar un decorador como este memorizador para esas funciones, asumiendo que siempre devuelven el mismo valor. Tenga en cuenta que puede llamar a expensive_foo y expensive_bar tantas veces como desee y que el cuerpo de la función solo se ejecuta una vez

 def memoize(f): mem = {} def inner(*args): if args not in mem: mem[args] = f(*args) return mem[args] return inner @memoize def expensive_foo(): print "expensive_foo" return False @memoize def expensive_bar(): print "expensive_bar" return True if expensive_foo(): a=expensive_foo() print "FOO" elif expensive_bar(): a=expensive_bar() print "BAR" 

Resolvería esto de la misma manera que resuelvo otros problemas del control de flujo complicado: mueva el bit complicado a una función, y luego use el return para provocar una salida temprana de la función. Al igual que:

 def do_correct_something(): a = something() if a: # do something with a return a a = somethingelse() if a: # do something else with a return a # 5 more function calls, if statements, do somethings, and returns # then, at the very end: return None a = do_correct_something() 

El otro “problema complicado del control de flujo” para el que hago esto es salir de varios bucles nesteds:

 def find_in_3d_matrix(matrix, x): for plane in matrix: for row in plane: for item in row: if test_function(x, item): return item return None 

También puede resolver la pregunta formulada escribiendo un bucle for que solo se repetirá una vez, y use “break” para la salida temprana, pero prefiero la versión con función de retorno. Es menos complicado y más claro lo que está sucediendo; y la función con retorno es la única forma limpia de romper varios bucles en Python. (Poner ” if break_flag: break ” en cada uno de los bucles for, y configurar break_flag cuando se quiere romper, no está limpio en mi humilde opinión).

Esto es posible si trabajamos con cadenas, ya que podemos convertir una cadena en una lista y usar el método extends para una lista que hace lógicamente agregar en línea una cadena a otra (en formato de lista):

 >>> my_str = list('xxx') >>> if not my_str.extend(list('yyy')) and 'yyy' in ''.join(my_str): ... print(True) True 

Aquí estamos dentro if ‘agregamos datos nuevos a la cadena original’ e intentamos encontrarlos. Tal vez esto sea feo, pero esto es una asignación en una expresión como:

 if my_str += 'yyy' and 'yyy' in my_str: 

A partir de Python 3.8.0a1 +, podemos usar la syntax de la expresión de asignación .

Por ejemplo:

 >>> if a := 0: ... print('will not be printed') ... elif a := 1: ... print('print value of a: %d, a should be 1' % a) ... else: ... print('will not be printed') ... print value of a: 1, a should be 1