Je pensais récemment au code suivant et je me demandais ce qui n'allait pas. Auparavant, j'utilisais la méthode .get des dictionnaires avec succès, mais maintenant je voulais aussi passer des arguments et c'est là que j'ai remarqué un comportement quelque peu étrange:

def string_encoder(nmstr):
    return nmstr.encode('UTF-8')

def int_adder(nr_int):
    return int(nr_int) + int(nr_int)

def selector(fun, val):
    return {'str_en': string_encoder(val), 
            'nr_add': int_adder(val)}.get(fun, string_encoder(val))
selector('str_en', 'Test') -> ValueError
selector('str_en', 1) -> AttributeError

Le code ci-dessus ne s'exécutera jamais. Pour inspecter le problème, j'ai fourni un petit morceau de code:

def p1(pstr):
    print('p1: ', pstr)
    return pstr

def p2(pstr):
    print('p2: ', pstr)
    return pstr

def selector_2(fun, val):
    return {'p1': p1(val), 
            'p2': p2(val)}.get(fun, p2(val))
selector_2('p1', 'Test')
Out[]: p1:  Test
       p2:  Test
       p2:  Test
       'Test'

Je m'attendrais à ce que le .get ('p1', 'test') suivant produise le test 'p1: test'. Mais comme il me semble, chaque argument est évalué, même s'il n'est pas sélectionné. Ma question est donc la suivante: pourquoi chaque argument est-il évalué avec la méthode .get ou comment expliquer ce comportement?

0
Hans T 15 mars 2019 à 20:58

2 réponses

Meilleure réponse

La création dict est impatiente, tout comme l'évaluation des arguments. Donc, avant même que get ne s'exécute, vous avez appelé string_encoder deux fois et int_adder une fois (et comme les comportements sont largement orthogonaux, vous obtiendrez une erreur pour tout sauf un {{{ X4}} comme "123").

Vous devez éviter d'appeler la fonction jusqu'à ce que vous sachiez laquelle appeler (et idéalement, n'appelez cette fonction qu'une seule fois).

La solution la plus simple consiste à faire en sorte que les appels dict et get contiennent les fonctions elles-mêmes, plutôt que le résultat de leur appel; vous vous retrouverez avec la fonction gagnante, et vous pourrez ensuite appeler cette fonction. Par exemple:

def selector(fun, val):
    # Removed (val) from all mentions of functions
    return {'str_en': string_encoder, 
            'nr_add': int_adder}.get(fun, string_encoder)(val) # <- But used it to call resulting function

Étant donné que string_encoder est votre valeur par défaut, vous pouvez supprimer entièrement la gestion de 'str_en' pour simplifier:

    return {'nr_add': int_adder}.get(fun, string_encoder)(val)

Ce qui conduit à réaliser que vous ne retirez vraiment rien du dict. Les dict ont une recherche bon marché, mais vous reconstruisez le dict à chaque appel, vous n'avez donc rien enregistré. Étant donné que vous n'avez vraiment que deux comportements:

  1. Appelez int_adder si fun est 'nr_add'
  2. Sinon, appelez string_encoder

La bonne solution est juste une vérification if qui est plus efficace et plus facile à lire:

def selector(fun, val):
    if fun == 'nr_add':
        return int_adder(val)
    return string_encoder(val)

    # Or if you love one-liners:
    return int_adder(val) if fun == 'nr_add' else string_encoder(val)

Si votre vrai code a beaucoup d'entrées dans le dict, pas seulement deux, dont une est inutile, alors vous pouvez utiliser un dict pour les performances, mais construisez-le une fois à portée globale et référencez-la dans la fonction afin de ne pas la reconstruire à chaque appel (ce qui perd tous les avantages de performance de dict), par exemple:

# Built only once at global scope
_selector_lookup_table = {
    'str_en': string_encoder, 
    'nr_add': int_adder,
    'foo': some_other_func,
    ...
    'baz': yet_another_func,
    }

def selector(fun, val):
    # Reused in function for each call
    return _selector_lookup_table.get(fun, default_func)(val)
1
ShadowRanger 15 mars 2019 à 18:15

Si vous voulez éviter l'évaluation des fonctions et que choisit la fonction, faites-le à la place pour votre deuxième bloc (la syntaxe fonctionnera également pour votre premier bloc):

def selector_2(fun, val):
    return {'p1': p1, 
            'p2': p2}.get(fun)(val)
1
Rocky Li 15 mars 2019 à 18:04