Asignación elegante de Python basada en valores verdaderos / falsos

Tengo una variable que quiero establecer dependiendo de los valores en tres booleanos. La forma más directa es una sentencia if seguida de una serie de elifs:

if a and b and c: name = 'first' elif a and b and not c: name = 'second' elif a and not b and c: name = 'third' elif a and not b and not c: name = 'fourth' elif not a and b and c: name = 'fifth' elif not a and b and not c: name = 'sixth' elif not a and not b and c: name = 'seventh' elif not a and not b and not c: name = 'eighth' 

Esto es un poco incómodo, y me pregunto si hay una manera más Pythonic de manejar este problema. Un par de ideas vienen a la mente.

  1. Diccionario hack:

     name = {a and b and c: 'first', a and b and not c: 'second', a and not b and c: 'third', a and not b and not c: 'fourth', not a and b and c: 'fifth', not a and b and not c: 'sixth', not a and not b and c: 'seventh', not a and not b and not c: 'eighth'}[True] 

Lo llamo piratear porque no soy demasiado loco porque siete de las claves son Falsas y se anulan unas a otras.

  1. Y / o magia

     name = (a and b and c and 'first' or a and b and not c and 'second' or a and not b and c and 'third' or a and not b and not c and 'fourth' or not a and b and c and 'fifth' or not a and b and not c and 'sixth' or not a and not b and c and 'seventh' or not a and not b and not c and 'eighth') 

Esto funciona porque Python, ands y ors, devuelven el último valor para ser evaluado, pero hay que saberlo para comprender este código, de lo contrario, extraño.

Ninguna de estas tres opciones es muy satisfactoria. ¿Que recomiendas?

Puede pensar que a, b, y c son tres bits que cuando se juntan forman un número entre 0 y 7. Luego, puede tener una matriz de los valores [‘primero’, ‘segundo’, … ‘octavo’ ] y use el valor de bit como un desplazamiento en la matriz. Esto sería solo dos líneas de código (una para ensamblar los bits en un valor de 0 a 7, y una para buscar el valor en la matriz).

Aquí está el código:

 nth = ['eighth', 'seventh', 'sixth', 'fifth', 'fourth', 'third', 'second', 'first'] nth[(a and 4 or 0) | (b and 2 or 0) | (c and 1 or 0)] 

¿Qué hay de usar un dict?

 name = {(True, True, True): "first", (True, True, False): "second", (True, False, True): "third", (True, False, False): "fourth", (False, True, True): "fifth", (False, True, False): "sixth", (False, False, True): "seventh", (False, False, False): "eighth"} print name[a,b,c] # prints "fifth" if a==False, b==True, c==True etc. 

Tal vez no sea mucho mejor, pero ¿qué tal

 results = ['first', 'second', 'third', 'fourth', 'fifth', 'sixth', 'seventh', 'eighth'] name = results[((not a) << 2) + ((not b) << 1) + (not c)] 

si a, b, c son realmente booleanos:

 li = ['eighth', 'seventh', 'sixth', 'fifth', 'fourth', 'third', 'second', 'first'] name = li[a*4 + b*2 + c] 

si no son booleanos:

 li = ['eighth', 'seventh', 'sixth', 'fifth', 'fourth', 'third', 'second', 'first'] a,b,c = map(bool,(a,b,c)) name = li[a*4 + b*2 + c] 

idea de Clint Miller

Desde que obtuviste todas las combinaciones, podrías crear un índice basado en valores como este:

 def value(a,b,c ): values = ['8th','7th','6th','5th','4th','3rd','2nd','1st'] index = ( 4 if a else 0 ) + ( 2 if b else 0 ) + ( 1 if c else 0 ) return values[index] if __name__ == "__main__": print value(True, True, True ) print value(True, True, False ) print value(True, False, True ) print value(True, False, False ) print value(False, True, True ) print value(False, True, False) print value(False, False, True ) print value(False, False, False) 

salida:

 1st 2nd 3rd 4th 5th 6th 7th 8th 

¿Qué pasa con los ifs nesteds? Significa que no tiene que revisar todo varias veces y me lee con más claridad (aunque tal vez no sea tan inteligente como algunas de las otras respuestas):

 if a: if b: if c: name="first" else: name="second" else: if c: name="third" else: name="fourth" else: if b: if c: name="fifth" else: name="sixth" else: if c: name="seventh" else: name="eighth" 

Otra opción sería crear una función auxiliar:

 def first_true(*args): true_vals = (arg for arg in args if arg[0]) return next(true_vals)[1] name = first_true((a and b and c, 'first'), (a and b and not c, 'second'), (a and not b and c, 'third'), (a and not b and not c, 'fourth'), (not a and b and c, 'fifth'), (not a and b and not c, 'sixth'), (not a and not b and c, 'seventh'), (not a and not b and not c, 'eighth')) 

Este método asume que una de las pruebas aprobadas será verdadera. También podría hacerse más perezoso con lambdas.

Para medir velocidades:

 from time import clock a,b,c = True,False,False A,B,C,D,E,F,G,H = [],[],[],[],[],[],[],[] for j in xrange(30): te = clock() for i in xrange(10000): name = (a and b and c and 'first' or a and b and not c and 'second' or a and not b and c and 'third' or a and not b and not c and 'fourth' or not a and b and c and 'fifth' or not a and b and not c and 'sixth' or not a and not b and c and 'seventh' or not a and not b and not c and 'eighth') A.append(clock()-te) te = clock() for i in xrange(10000): if a and b and c: name = 'first' elif a and b and not c: name = 'second' elif a and not b and c: name = 'third' elif a and not b and not c: name = 'fourth' elif not a and b and c: name = 'fifth' elif not a and b and not c: name = 'sixth' elif not a and not b and c: name = 'seventh' elif not a and not b and not c: name = 'eighth' B.append(clock()-te) #===================================================================================== li = ['eighth', 'seventh', 'sixth', 'fifth', 'fourth', 'third', 'second', 'first'] te = clock() for i in xrange(10000): name = li[a*4 + b*2 + c] C.append(clock()-te) #===================================================================================== nth = ['eighth', 'seventh', 'sixth', 'fifth', 'fourth', 'third', 'second', 'first'] te = clock() for i in xrange(10000): name = nth[(a and 4 or 0) | (b and 2 or 0) | (c and 1 or 0)] D.append(clock()-te) nth = ['eighth', 'seventh', 'sixth', 'fifth', 'fourth', 'third', 'second', 'first'] te = clock() for i in xrange(10000): name = nth[(a and 4 or 0) + (b and 2 or 0) + (c and 1 or 0)] E.append(clock()-te) #===================================================================================== values = ['eighth', 'seventh', 'sixth', 'fifth', 'fourth', 'third', 'second', 'first'] te = clock() for i in xrange(10000): name = values[( 4 if a else 0 )| ( 2 if b else 0 ) | ( 1 if c else 0 )] F.append(clock()-te) values = ['eighth', 'seventh', 'sixth', 'fifth', 'fourth', 'third', 'second', 'first'] te = clock() for i in xrange(10000): name = values[( 4 if a else 0 ) + ( 2 if b else 0 ) + ( 1 if c else 0 )] G.append(clock()-te) #===================================================================================== dic = {(True, True, True): "first", (True, True, False): "second", (True, False, True): "third", (True, False, False): "fourth", (False, True, True): "fifth", (False, True, False): "sixth", (False, False, True): "seventh", (False, False, False): "eighth"} te = clock() for i in xrange(10000): name = dic[a,b,c] H.append(clock()-te) print min(A),'\n', min(B),'\n\n', min(C),'\n\n', min(D),'\n',min(E),'\n\n',min(F),'\n', min(G),'\n\n', min(H) 

Resultado

 0.0480533140385 0.0450973517584 0.0309056039245 0.0295291720037 0.0286550385594 0.0280122194301 0.0266760160858 0.0249769174574 

Iría por la solución de lista / bits de @OscarRyz, @Clint y @Sven, pero aquí hay otra:

 S1 = frozenset(['first', 'second', 'third', 'fourth']) S2 = frozenset(['first', 'second', 'fifth', 'sixth']) S3 = frozenset(['first', 'third', 'fifth', 'seventh']) last = 'eighth' empty = frozenset([]) 

S1 = frozenset(['first', 'second', 'third', 'fourth']) S2 = frozenset(['first', 'second', 'fifth', 'sixth']) S3 = frozenset(['first', 'third', 'fifth', 'seventh']) last = 'eighth' empty = frozenset([])

valor def (a, b, c):
para r en (a y S1 o vacío) & (b y S2 o vacío) & (c y S3 o vacío):
volver r
regreso pasado

Si su objective es evitar escribir muchos “ands” y expresiones booleanas, puede usar un número primo y solo una condición como esta (ejemplo para 2 condiciones)

  cond = (2**cond_1)*(3**cond_2) 

asi que

 cond == 1 #means cond_1 and cond_2 are False cond == 2 #means cond_1 is True and con_2 is False cond == 3 #means cond_1 is False and con_2 is True cond == 6 #means con_1 and Con_2 are True 

Este truco puede usarse para 3 condiciones usando 3 primes y así sucesivamente

Me gusta esto…

 cond = (2**a)*(3**b)*(5**c) name = {30:'first', 6: 'second', 10:'third', 2:'fourth', 15:'fifth', 3:'sixth', 5:'seventh', 1:'eighth'}[cond] 

Aquí hay un enfoque de tabla de verdad:

 lookup = {'000': 'eighth', '001': 'seventh', '010': 'sixth', '011': 'fifth', '100': 'fourth', '101': 'third', '110': 'second', '111': 'first'} def key(a, b, c): return ''.join([str(a),str(b),str(c)]) name = lookup[key(0,1,1)]