2009-10-03 7 views
3

selon this page on ne peut pas utiliser le codeQue devrais-je utiliser à la place de l'assignation-dans-une-expression en Python?

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

Donc, si je veux avoir la structure de code suivant:

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

où les fonctions quelque chose() sont de calcul intensif (je veux dire que l'utilisation de la fonction et ensuite le faire à nouveau pour assigner une valeur à une variable au cas où le premier était vrai ne peut pas être fait), que devrais-je écrire à la place en Python? Ajouter 7 autres variables au lieu de 1?

+1

Je ne comprends pas votre problème. Essayez-vous d'éviter d'écrire une instruction d'affectation explicite séparée de l'instruction if? Si oui, pourquoi? Quel est le problème avec la déclaration supplémentaire? –

+1

@ S.Lott: Je peux penser à quelques utilisations pour cela, car c'est un idiome de codage C-style assez standard. Il est souvent utilisé dans les boucles, par exemple: while ((p = getNextPointer())! = NULL) {// use p}. La déclaration supplémentaire, dans de nombreux cas, rend le code beaucoup plus laid (ce qui explique pourquoi c'est si courant, je suppose). –

+1

@ S.Lott: dans mon exemple de code, les 5 dernières fonctions ne sont pas exécutées si la seconde renvoie une vraie réponse, c'est un énorme avantage de performance comparé au calcul et à l'assignation de tout – Fluffy

Répondre

10

j'avais ce problème il y a des années, en 2001 - depuis que je était translittérer à Python à partir d'un algorithme de référence en C qui utilisait fortement assign-and-test, je tenais à garder une structure similaire pour le premier brouillon (puis refactoriser plus tard une fois que la correction fut bien testée).J'ai donc écrit un recipe dans le livre de recettes (voir aussi here), qui se résume à ...:

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

si l'arbre if/elif peut devenir:

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

la classe DataHolder peut clairement être embelli de différentes façons (et est tellement embelli dans les deux versions en ligne et livre), mais c'est l'essentiel de celui-ci, et tout à fait suffisant pour répondre à votre question.

3

Vous pouvez le faire:

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

Ou, à l'intérieur d'une fonction, vous pouvez limiter le niveau d'imbrication avec un return dans chaque cas de correspondance:

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

Une autre alternative qui offre une certaine flexibilité:

# 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 

Ou vous pouvez ajouter explicitement à la fonction:

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 certaines fonctions prennent des arguments, vous pouvez utiliser lambda pour maintenir le paradigme de la fonction:

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: 
    # ... 
1

Vous pouvez utiliser un décorateur comme celui-ci mémorisent pour ces fonctions - en supposant qu'ils reviennent toujours les mêmes valeur. Notez que vous pouvez appeler expensive_foo et expensive_bar autant de fois que vous le souhaitez et le corps de la fonction ne jamais exécutée une fois gets

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" 
+0

Cette astuce est généralement appelée "mémoization" (ou "mémoisation" si vous utilisez l'anglais de la reine). Non 'r'. Références: http://en.wikipedia.org/wiki/Memoization http://antoniocangiano.com/2009/05/18/memoization-in-ruby-and-python/ – steveha

1

je pourrais manquer quelque chose, mais ne pourrais pas vous tenir chacune des branches dans votre top- niveau if instruction dans des fonctions séparées, créer une liste de test à action tuples et boucle sur eux? Vous devriez pouvoir appliquer ce modèle pour imiter la logique de style if (value=condition()) {} else if (value=other_condition()) {}.

Ceci est vraiment une extension de redglyph's response et peut probablement être compressé jusqu'à un iterator qui soulève StopIteration une fois qu'il a atteint une valeur de vérité.

# 
# 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) 
) 
2

Assurez-vous d'un simple objet appelable qui enregistre sa valeur retournée:

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

utiliser maintenant comme ceci:

# 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 

Prints:

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 
1

Je résoudrais cela de la même façon que je résous plusieurs autres problèmes de contrôle de flux difficile: déplacez le bit délicat dans une fonction, puis utilisez return pour provoquer une sortie anticipée de la fonction. Comme si:

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

La principale autre « problème délicat du contrôle de flux » pour lequel je fais est la rupture de plusieurs boucles imbriquées:

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 

Vous pouvez également résoudre la question posée par la rédaction d'un pour la boucle qui ne va parcourir qu'une seule fois, et utiliser "break" pour la sortie anticipée, mais je préfère la version "function-with-return". C'est moins délicat, et plus clair ce qui se passe; et la fonction-avec-retour est le seul moyen propre de sortir de plusieurs boucles en Python. (Mettre "if break_flag: break" dans chacune des boucles for, et définir break_flag quand vous voulez casser, n'est pas propre IMHO.)

+0

Peut être écrit comme 'retour suivant (item pour plan dans la matrice pour la rangée dans le plan pour l'élément dans la rangée si test_function (x, item)) ' – PaulMcG

+0

@PaulMcG Je pense que la fonction est plus facile à comprendre et plus pratique. Lors du débogage, vous pouvez choisir d'intervenir ou de passer d'une fonction à une autre; cette expression de générateur n'est pas aussi pratique. Mais si vous préférez que votre programme utilise la machinerie du générateur pour éviter un appel de fonction, cela fonctionnerait. – steveha

0

Ceci est possible si nous travaillons avec des chaînes - parce que nous pouvons convertir la chaîne en liste et utiliser la méthode extends pour la liste qui font logiquement une chaîne en ligne annexant à un autre (sous forme de liste):

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

ici, nous if à l'intérieur «a ajouté à la chaîne d'origine » de nouvelles données et essayer de le trouver. Peut-être que c'est moche mais c'est assignation dans une expression comme:

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