Journal Python et valeurs par défaut des paramètres

Posté par (page perso) . Licence CC by-sa
Tags :
11
31
juil.
2012

Bonjooour les zamis [ bienvenue au pays trop mignon ]

Au cours de mes pérégrination pythranesques, j'ai découvert une fleur du langage python, que je m'empresse de partager.

D'après vous, qu'affiche la séquence suivante ?

def foo(a=list()): return a
foo().append(1)
foo().append(2)
print foo()

Et hop une manière détournée (et hideuse, elle n'a donc pas à sa place au pays trop mignon) d'avoir des variables statiques dans une fonction en python.

  • # C'est très connu

    Posté par (page perso) . Évalué à 5.

    Ça doit même être dans le tutoriel de Guido Van Rossum (je regarderai). C'est pourquoi il est conseillé de toujours utiliser des objets "immutable" en tant que valeur par défaut. Quand je forme des débutants, je présente ça comme un écueil classique.

  • # J'aime pas ce comportement

    Posté par (page perso) . Évalué à 5.

    la liste a devient un objet attaché à l'objet fonction 'foo' qui est réutilisé à chaque appel

    C'est vicieux car on a l'impression de donner une valeur par défaut et ce n'est pas le cas.

    http://www.artfulcode.net/articles/mutable-default-parameter-values-python/

    Du coup pour les valeurs par défaut, il est préconisé de mettre None et de traiter le cas das la fonction:

    def append_one(a=list()):
        a.append(1)
        return a
    
    

    deviendrait

    def append_one(a=None):
        if a is None:
            a = list()
        a.append(1)
        return a
    
    
    • [^] # Re: J'aime pas ce comportement

      Posté par (page perso) . Évalué à 0.

      Oui, j'ai vu cet idiome de nombreuses fois. Ça ressemble furieusement à ça http://en.wikipedia.org/wiki/Option_type.

      • [^] # Re: J'aime pas ce comportement

        Posté par (page perso) . Évalué à 2.

        L'implémentation des Options en Scala est sympa. C'est très utile comme idiome car ça aide à éviter des segfault/NullPointerException/None qu'on a trop souvent dans les autres langages.

      • [^] # Re: J'aime pas ce comportement

        Posté par (page perso) . Évalué à 4.

        Pas du tout.
        Un type option permet de distinguer "None" de "Just None" (ou de "Some None"), alors que là, c'est impossible.

      • [^] # Re: J'aime pas ce comportement

        Posté par (page perso) . Évalué à 1.

        Pour les options j'utiliserais plutôt un truc de ce style:

        def append_one(*args, **kwords):
            a = kwords.get("a", list())
            a.append(1)
            return a
        
        append_one(a=[1, 2, 3])
        append_one()
        append_one(a = None) # raise AttributeError: 'NoneType' object has no attribute 'append'
        
        

        Matthieu Gautier|irc:starmad

    • [^] # Re: J'aime pas ce comportement

      Posté par (page perso) . Évalué à 6.

      En fait si on réfléchis aux instructions "def" non pas comme des définitions de fonctions mais comme des créations de singleton avec un opérateur (), on arrive à se mettre ce comportement dans la tête.
      Cela dit, c'est vraiment contre-intuitif. Au point que je me demande s'il n'aurait pas été plus malin d'interdire (ou de mettre un warning lorsque la valeur par défaut est mutable) cela.
      C'est certes parfois utilisé pour maintenir un état dans une fonction, mais c'est plus un hack qu'autre chose et ça contredit le moto "Explicit is better than implicit.".

      Quelqu'un sait si la pertinence de ce comportement a été officiellement discutée ?

      • [^] # Re: J'aime pas ce comportement

        Posté par . Évalué à 1.

        Le problème c'est qu'on ne peut pas vraiment savoir quel objet est mutable ou pas, puisque tu peux rajouter des méthodes après que des objets soit créés par exemple, ça pousserai aussi a faire une analyse du type de l'objet au moment de l'exécution du def, ce qui assez complexe dans le cas de python (et je ne parle pas des objets implémentés en C), ensuite comme les attributs sont modifiables par tout le monde, tu est obligé de faire une analyse statique sur tout ton programme.

        En résumé pas possible :)

  • # la valeur par défaut n'est évaluée qu'une fois

    Posté par . Évalué à 5.

    la valeur par défaut n'est évaluée qu'une fois, qu'elle soit mutable ou non …

    >>> i=1
    >>> def bar(j=i): return j
    
    >>> bar()
    1
    
    >>> i=2
    >>> bar()
    1
    
    
    • [^] # Re: la valeur par défaut n'est évaluée qu'une fois

      Posté par (page perso) . Évalué à 5.

      C'est même extrêmement important à comprendre au sujet des lambda :

      i = 1
      
      # i est résolu dans l'espace de nommage courant
      lambda1 = lambda : i
      
      # i est résolu une fois à la création de la lambda et c'est ensuite le i "local" qui est résolu à l'execution
      lambda2 = lambda i=i: i
      
      print "lambda1:",lambda1() # 1 (i global)
      print "lambda2:",lambda2() # 1 (i à la création)
      
      i = 2
      
      print "lambda1:",lambda1() # 2 
      print "lambda2:",lambda2() # 1
      
      

      Et en beaucoup plus vicieux :

      def create_mult_functions_bad(coefs):
          # toutes les lambda utilisent le même coef.
          # il est résolu a l’exécution des lambda. À ce moment là coef == coefs[-1]
          return [lambda val : val*coef for coef in coefs]
      
      def create_mult_functions(coefs):
          # coef est résolu à la création de la lamdba
          return [lambda val, coef=coef: val*coef for coef in coefs]
      
      mult1, mult2, mult3 = create_mult_functions_bad([1,2,3])
      
      print mult1(1) # 3
      print mult2(1) # 3
      print mult3(1) # 3
      
      
      
      mult1, mult2, mult3 = create_mult_functions([1,2,3])
      
      print mult1(1) # 1
      print mult2(1) # 2
      print mult3(1) # 3
      
      

      Matthieu Gautier|irc:starmad

      • [^] # Re: la valeur par défaut n'est évaluée qu'une fois

        Posté par (page perso) . Évalué à 4.

        J'ai dit lambda mais c'est exactement la même chose avec les fonction

        i = 1
        
        def f1():
            return i
        
        def f2(i=i):
            return i
        
        print f1() #1
        print f2() #1
        
        i=2
        
        print f1() #2
        print f2() #1
        
        def create_mult_functions_bad(coefs):
            ret = []
            for coef if coefs:
                def f(val):
                    return val*coef
            return ret
        
        def create_mult_functions(coefs):
            ret = []
            for coef if coefs:
                def f(val, coef=coef):
                    return val*coef
            return ret
        
        mult1, mult2, mult3 = create_mult_functions_bad([1,2,3])
        
        print mult1(1) # 3
        print mult2(1) # 3
        print mult3(1) # 3
        
        mult1, mult2, mult3 = create_mult_functions([1,2,3])
        
        print mult1(1) # 1
        print mult2(1) # 2
        print mult3(1) # 3
        
        

        Matthieu Gautier|irc:starmad

  • # Quelle version de python ?

    Posté par (page perso) . Évalué à -1.

    Chez moi le code ne marche pas.

    En utilisant Python 2.7 et pas de version 3.x :

    >>> def foo(a=list()): return a
    ... foo().append(1)
      File "<stdin>", line 2
        foo().append(1)
          ^
    SyntaxError: invalid syntax
    
    

    Commentaire sous licence LPRAB - http://sam.zoy.org/lprab/

    • [^] # Re: Quelle version de python ?

      Posté par . Évalué à 6.

      quand tu utilises l'interpréteur, tu dois mettre une ligne vide après la définition de la fonction :

      def foo(a=list()): return a
      
      foo().append(1)
      foo().append(2)
      
      

      Tous les nombres premiers sont impairs, sauf un. Tous les nombres premiers sont impairs, sauf deux.

  • # coloration syntaxique

    Posté par . Évalué à 2. Dernière modification le 31/07/12 à 12:18.

    Je découvre qu'on peut faire la coloration syntaxique pour bash sous dlfp, merci :)

    test:

    #!/bin/bash
    if [ $test -gt 1 ]
    then 
    do echo hello world  ;
    done
    fi
    
    
    • [^] # Re: coloration syntaxique

      Posté par (page perso) . Évalué à 3.

      Tu vis sous l'eau? La coloration était là depuis le début de la version ROR.

      « Rappelez-vous toujours que si la Gestapo avait les moyens de vous faire parler, les politiciens ont, eux, les moyens de vous faire taire. » Coluche

      • [^] # Re: coloration syntaxique

        Posté par . Évalué à 2.

        Je savais pour le ruby, j'ignorais pour les autres langages. Au fait lesquels supporte t-il encore ?

        • [^] # Re: coloration syntaxique

          Posté par (page perso) . Évalué à 3.

          Je ne sais plus quelle est la bibliothèque utilisée (j'ai http://pygments.org/ en tête, mais comme c'est en python, j'ai un doute). Mais ça couvre beaucoup de langages, de mémoire, C, C++, Java, Go, Python, Ruby, sh sont couvert et le seul pour lequel j'ai eu un problème, c'est Rust qui n'est pas encore prix en charge.

          « Rappelez-vous toujours que si la Gestapo avait les moyens de vous faire parler, les politiciens ont, eux, les moyens de vous faire taire. » Coluche

  • # Autres syntaxes

    Posté par (page perso) . Évalué à 7.

    Au cours de mes pérégrination pythranesques, j'ai découvert une fleur du langage python, que je m'empresse de partager.

    Ce n'est pas vraiment une "fonctionnalitée" cachée. Il y a d'autres manières d'avoir des variables locales pour une fonction.

    Exemple avec une fermeture (closure) :

    >>> def with_locals():
    ...  accu = []
    ...  def func():
    ...   accu.append(1)
    ...   print(accu)
    ...  return func
    ... 
    >>> func = with_locals()
    >>> func()
    [1]
    >>> func()
    [1, 1]
    >>> func()
    [1, 1, 1]
    >>> func()
    [1, 1, 1, 1]
    
    

    À partir de Python 3, on peut également modifier la variable (et non pas juste son contenu) grâce à nonlocal :

    >>> def with_locals():
    ...  x = 0
    ...  def func():
    ...   nonlocal x
    ...   x += 1
    ...   print(x)
    ...  return func
    ... 
    >>> func = with_locals()
    >>> func()
    1
    >>> func()
    2
    >>> func()
    3
    
    

    On peut également utiliser des attributs arbitraires à la fonction pour y stocker des trucs :

    >>> def hello():
    ...  print(hello.world)
    ... 
    >>> hello.world="Hello World!"
    >>> hello()
    Hello World!
    >>> hello.world="Bonjour Monde !"
    >>> hello()
    Bonjour Monde !
    
    

    (La fonction peut également modifier hello.world)

Suivre le flux des commentaires

Note : les commentaires appartiennent à ceux qui les ont postés. Nous n'en sommes pas responsables.