IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Python Discussion :

comportement que je ne comprend pas


Sujet :

Python

  1. #1
    Nouveau membre du Club
    Inscrit en
    Novembre 2008
    Messages
    46
    Détails du profil
    Informations forums :
    Inscription : Novembre 2008
    Messages : 46
    Points : 33
    Points
    33
    Par défaut comportement que je ne comprend pas
    Bonjour,

    j'ai un comportement que je ne comprend pas dans ce petit exemple:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    def test(toto = [0.,1.]):
        toto.append(2.)
        print toto
        return toto
     
    test1 = test()
    print 'test1',test1
     
    test2 = test()
    print 'test2',test2
    Pourquoi les deux appelle à la fonction test ne renvoient pas la même chose !!!
    a savoir
    [0.,1.,2.]
    puis [0.,1.,2.,2.] !

    Pour que ca fonctionne je fait
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    def test(toto = None):
        if toto == None : toto = [0.,1.]     
        toto.append(2.)
        print toto
        return toto
    On m'avait déjà donné cette astuce dans un autre cas de figure.

    Quelqu'un saurait m'expliquer pourquoi la première version n'est pas correct, et pourquoi les concepteurs de python ont souhaité ceci?

    Cordialement,

  2. #2
    Membre actif
    Profil pro
    Inscrit en
    Avril 2008
    Messages
    159
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Avril 2008
    Messages : 159
    Points : 224
    Points
    224
    Par défaut
    Salut,
    C'est très connu, voir ici http://docs.python.org/tutorial/cont...rgument-values le paragraphe "important warning".

    Python n'évalue les arguments par défaut qu'une seule fois, lors des appels suivants de la fonction il se contente d'accéder à l'objet instancié la première fois.
    Si l'objet en question est mutable (comme une liste), il peut très bien avoir été modifié entretemps (ce qui est le cas dans ton exemple).
    Je ne sais pas ce qui a motivé ce fonctionnement, mais je suppose qu'il s'agissait à l'origine plus d'une facilité d'implémentation qu'autre chose.

    PS : en tous cas, la méthode que tu utilises pour contourner le problème correspond bien à ce qui est recommandé dans le "python tutorial"

  3. #3
    Membre éprouvé
    Homme Profil pro
    Inscrit en
    Décembre 2007
    Messages
    758
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : France

    Informations professionnelles :
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Décembre 2007
    Messages : 758
    Points : 970
    Points
    970
    Par défaut
    bonjour,

    en fait l'explication tient en une seule phrase:

    les valeurs par défaut des arguments d'une fonction sont crées une seule fois à la déclaration de cette dernière.

    donc, en affectant un type muable en valeur par défaut, cette valeur reste la même pendant toute la durée de vie de la fonction.

    ce qui explique dans ton cas que la valeur de la liste change au cours du temps et des appels à la fonction.

    la solution consiste à ne pas affecter des types muables en valeur par défaut.

    ça c'est pour le comment. pour le pourquoi, je ne suis pas dans les petits papiers des développeurs de Python. mais, de mon point de vue il ne s'agit pas d'un choix en tant que tel mais plus de la conséquence d'un autre choix, plus important, fait sur l'architecture générale de Python.

    edit: grilled

  4. #4
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 287
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 287
    Points : 36 776
    Points
    36 776
    Par défaut
    Salut,
    La valeur des arguments par défaut n'est évaluée qu'à la definition de la fonction i.e. une fois... Ce qui peut provoquer des surprises lorsqu'il est "mutable" (une "list").

    C'est expliqué dans la doc :
    Important warning: The default value is evaluated only once. This makes a difference when the default is a mutable object such as a list, dictionary, or instances of most classes. For example, the following function accumulates the arguments passed to it on subsequent calls:
    Et aussi dans la description de ce qu'est une "fonction" en Python
    Default parameter values are evaluated when the function definition is executed. This means that the expression is evaluated once, when the function is defined, and that that same “pre-computed” value is used for each call. This is especially important to understand when a default parameter is a mutable object, such as a list or a dictionary: if the function modifies the object (e.g. by appending an item to a list), the default value is in effect modified. This is generally not what was intended.
    Côté design (pourquoi) la question est comment traiter l'initialisation d'arguments par défaut avec un type "mutable"...
    Le choix Python est surprenant pour la première fois qu'on tombe dessus, mais il est assez cohérent avec 'tout est objet' - et les fonctions aussi - et le traitement des "valeurs"/"collections".
    Lire aussi effbot qui donne pas mal d'exemples.
    - W
    PS: Je n'ai pas trouvé de comparatif avec d'autres langages objets sur le traitement des paramètres par défaut: peu le proposent.
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  5. #5
    Nouveau membre du Club
    Inscrit en
    Novembre 2008
    Messages
    46
    Détails du profil
    Informations forums :
    Inscription : Novembre 2008
    Messages : 46
    Points : 33
    Points
    33
    Par défaut
    Merci à tous pour vos réponse très claire !

    Je ne trouve pas cela très naturel, mais bon, on peut bien pardonner ceci pour tout le reste !

    Merci encore

  6. #6
    Rédacteur
    Avatar de Zavonen
    Profil pro
    Inscrit en
    Novembre 2006
    Messages
    1 772
    Détails du profil
    Informations personnelles :
    Âge : 76
    Localisation : France

    Informations forums :
    Inscription : Novembre 2006
    Messages : 1 772
    Points : 1 913
    Points
    1 913
    Par défaut
    PS: Je n'ai pas trouvé de comparatif avec d'autres langages objets sur le traitement des paramètres par défaut: peu le proposent.
    Pour information C++ propose les arguments par défaut depuis sa création en 1983, alors que Python ne devait voir le jour que 7 ans plus tard. En outre le comportement est, de mon point de vue plus naturel. Il me semble que le c'est le choix d'implémentation des ppd en Python qui implique ce comportement somme toute bizarre.
    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #include <iostream>
     
    using namespace std;
    int fonc(int n2=0)
    {
        n2++;
        return n2;
    }
     
    int main()
    {
       cout<<fonc()<<"\n";
       cout<<fonc();
     
        return 0;
    }
    Le programme affiche bien
    1
    1
    Ce qu'on trouve est plus important que ce qu'on cherche.
    Maths de base pour les nuls (et les autres...)

  7. #7
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 287
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 287
    Points : 36 776
    Points
    36 776
    Par défaut
    Salut,

    Pour information C++ propose les arguments par défaut depuis sa création en 1983, alors que Python ne devait voir le jour que 7 ans plus tard.
    foo(p1='toto', p2='tutu') en Python n'a pas la même sémantique qu'en C++. En C++, les paramètres sont positionnels, alors qu'en Python, ils sont aussi "nommés". i.e. foo(p2='x') est possible en Python pas en C++


    En outre le comportement est, de mon point de vue plus naturel. Il me semble que le c'est le choix d'implémentation des ppd en Python qui implique ce comportement somme toute bizarre.
    Dans l'exemple que vous proposez le type d'un int n'est pas mutable, en Python çà fonctionnerait pareil.

    C# et DELPHI proposent aussi cela.
    Mais, je n'ai pas trouvé de comparatif sur les détails des subtilités sur 'que se passe-t-il lorsque le paramètre par défaut est 'mutable'.
    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  8. #8
    Membre expérimenté
    Homme Profil pro
    Inscrit en
    Mars 2007
    Messages
    941
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations forums :
    Inscription : Mars 2007
    Messages : 941
    Points : 1 384
    Points
    1 384
    Par défaut
    Cet exemple devrait être plus concluant (mon C++ est un peu rouillé, désolé si c'est une horreur):
    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #include <iostream>
    #include <vector>
     
    using namespace std;
     
    vector<int> *func(int x, vector<int> *v = new vector<int>)
    {
    	v->push_back(x);
    	return v;
    }
     
    int main()
    {
    	vector<int> *V1, *V2;
    	V1 = func(1); cout << &V1 << endl;
    	V2 = func(2); cout << &V2 << endl;
    	for (vector<int>::iterator x=V1->begin(); x < V1->end(); ++x)
    		cout << *x << " ";
    	return 0;
    }
    Résultat:
    Les adresses des vecteurs retournés sont différentes, et le premier vecteur n'a pas été modifié après le second appel. Les valeurs par défaut ne sont donc pas évaluées lors de l'initialisation statique mais lors de chaque appel, contrairement à Python.

  9. #9
    Membre expérimenté
    Homme Profil pro
    Inscrit en
    Mars 2007
    Messages
    941
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations forums :
    Inscription : Mars 2007
    Messages : 941
    Points : 1 384
    Points
    1 384
    Par défaut
    Question subsidiaire:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    funcs = [lambda i=i:i for i in range(10)]
    Si l'argument par défaut était évalué lors de l'appel en Python, ce code serait idiot et ne fonctionnerait plus comme actuellement. Comment pourrait-on obtenir le même résultat dans ce cas ?

  10. #10
    Expert éminent
    Homme Profil pro
    Big Data / Freelance EURL
    Inscrit en
    Mars 2003
    Messages
    2 124
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Big Data / Freelance EURL

    Informations forums :
    Inscription : Mars 2003
    Messages : 2 124
    Points : 7 291
    Points
    7 291
    Par défaut
    Bonjour,

    Et de la même façon comment on évite les interdépendances entre les attributs de 2 instances différentes de class identiques ? :-) (je n'y connais pas grand chose en objet)

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class toto:
         mavar=0
         def __init__(self):
             self.__class__.mavar=0
         def compte(self,count=0):
             print "compte avant",self.__class__.mavar
             self.__class__.mavar += 1  
             print "compte après",self.__class__.mavar
     
     
    a=toto()
    b=toto()
    a.compte()
    b.compte()
    donne malheureusement :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    compte avant 0
    compte après 1
    compte avant 1
    compte après 2
    alors que je devrais avoir 0,1 et 0,1.

  11. #11
    Rédacteur
    Avatar de Zavonen
    Profil pro
    Inscrit en
    Novembre 2006
    Messages
    1 772
    Détails du profil
    Informations personnelles :
    Âge : 76
    Localisation : France

    Informations forums :
    Inscription : Novembre 2006
    Messages : 1 772
    Points : 1 913
    Points
    1 913
    Par défaut
    Ici c'est complètement logique, mavar est une variable de classe et non une variable d'instance (donnée d'un objet de classe toto), elle est donc partagée.
    Ce code donne le comportement souhaité:
    Code python : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class toto:
        def __init__(self):
            self.mavar=0
        def compte(self,count=0):
            print "compte avant",self.mavar
            self.mavar += 1  
            print "compte après",self.mavar
    a=toto()
    b=toto()
    a.compte()
    b.compte()
    Ce qu'on trouve est plus important que ce qu'on cherche.
    Maths de base pour les nuls (et les autres...)

  12. #12
    Rédacteur
    Avatar de Zavonen
    Profil pro
    Inscrit en
    Novembre 2006
    Messages
    1 772
    Détails du profil
    Informations personnelles :
    Âge : 76
    Localisation : France

    Informations forums :
    Inscription : Novembre 2006
    Messages : 1 772
    Points : 1 913
    Points
    1 913
    Par défaut
    @dividee:
    Ton C++ n'est pas rouillé puisque tu sais encore utiliser des classes templates.
    J'avais envie de donner un exemple avec les listes de la librairie standard mais ton exemple est parfait pour ce qui concerne les objets 'mutables 'en dialecte python, il n'y a donc rien à ajouter.
    Cela dit la remarque de wiztricks concernant la position des arguments par défaut est parfaitement exacte. Les arguments par défaut ne sont pas à proprement parlé nommés en C++, du moins le nom qu'on donne n'a aucune importance. Ce qui impose une petite contrainte, quand on veut passer seulement un argument par défaut qui ne se trouve pas immédiatement après la liste normale, il faut répéter les valeurs par défauts des arguments intermédiaires.
    Pour ce qui concerne ta question, on ne peut pas dire qu'elle soit t posée de façon très intelligible.
    Enfin je crois comprendre que la réponse attendue est:
    Code python : Sélectionner tout - Visualiser dans une fenêtre à part
    funcs = [(lambda i:lambda: i) (i) for i in range(10)]
    Mais je n'ai aucun mérite à la proposer, nous avons sans doute les mêmes lectures (Chris Johnson: autogenerate functions -arrays of lambdas)
    Ce qu'on trouve est plus important que ce qu'on cherche.
    Maths de base pour les nuls (et les autres...)

  13. #13
    Membre expérimenté
    Homme Profil pro
    Inscrit en
    Mars 2007
    Messages
    941
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations forums :
    Inscription : Mars 2007
    Messages : 941
    Points : 1 384
    Points
    1 384
    Par défaut
    Oui j'ai peut-être mal posé la question, mais tu sembles l'avoir bien comprise. Je ne connaissais pas le post de Chris Johnson, j'ai simplement écrit ce qui m'est venu à l'esprit de plus concis qui utilise l'idiome "lambda x=x" et illustre le problème.

    Et pour être honnête je n'avais pas la réponse, mais j'espérais bien que quelqu'un la trouverait.

    Bravo à toi (et à Chris Johnson )

  14. #14
    Expert éminent
    Homme Profil pro
    Big Data / Freelance EURL
    Inscrit en
    Mars 2003
    Messages
    2 124
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Big Data / Freelance EURL

    Informations forums :
    Inscription : Mars 2003
    Messages : 2 124
    Points : 7 291
    Points
    7 291
    Par défaut
    Merci pour ta réponse
    Citation Envoyé par Zavonen Voir le message
    Ici c'est complètement logique, mavar est une variable de classe et non une variable d'instance (donnée d'un objet de classe toto), elle est donc partagée.
    Ce code donne le comportement souhaité:
    Code python : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    class toto:
        def __init__(self):
            self.mavar=0

  15. #15
    Expert éminent sénior
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 287
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 287
    Points : 36 776
    Points
    36 776
    Par défaut
    Salut,
    Je n'ai pas trop eu le temps de re-réfléchir à ce sujet.
    En gros, si une fonction à une variable nommé ayant pour défaut un "mutable".... Elle devient "objet".
    Au sens ou elle a une "mémoire": le résultat de l'appel N dépend de N-1.
    Ce qui peut être "perturbant" dans cette histoire, c'est qu'en général, pour donner une "mémoire" à une fonction, on utilise des méthodes qui sont cohérentes avec ce qu'on ferait avec une "classe".
    Exemple:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    def add(x):
         return lambda y, x=x: x+y
    # est comparable à:
    class add(object):
         def __init__(self, x):
              self._x = x
         def __call__(self, y):
              return y + self._x
    On a remplacé la mémoire locals() par une variable d'instance (self._x).
    Avec la variable nommé, la mémoire est add.func_default, le uplet initialisé par def... les effets de bords sont intéressants mais à part çà...
    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  16. #16
    Membre expérimenté
    Homme Profil pro
    Inscrit en
    Mars 2007
    Messages
    941
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations forums :
    Inscription : Mars 2007
    Messages : 941
    Points : 1 384
    Points
    1 384
    Par défaut
    Parler de "mémoire locals()" n'est pas tout a fait juste; le locals() de la lambda n'existe que lorsque celle-ci est en cours d'exécution, il me semble. Il y a des attributs particulier de l'objet fonction qui mémorisent les valeurs par défaut des arguments:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    >>> def add(x):
    ...      return lambda y, x=x: x+y
    >>> a = add(5)
    >>> a.func_defaults
    (5,)
    >>> a.func_code.co_varnames
    ('y', 'x')
    Il y a une troisième possibilité qui consiste à utiliser une fermeture. Là aussi des attributs de l'objet fonction constituent la "mémoire" de la fonction:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    def add(x):
         return lambda y: x+y
    >>> a = add(5)
    >>> a.func_closure
    (<cell at 0x024FD6D0: int object at 0x003922D8>,)
    >>> a.func_closure[0].cell_contents
    5
    >>> a.func_code.co_freevars
    ('x',)
    La fermeture est aussi une méthode classique pour "donner une mémoire" à une fonction, et c'est d'ailleurs celle que Zavonen a utilisé pour répondre à ma petite énigme un peu plus haut.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Requête ayant un comportement que je ne comprends pas
    Par Kropernic dans le forum Développement
    Réponses: 0
    Dernier message: 08/09/2011, 12h25
  2. Réponses: 3
    Dernier message: 18/01/2010, 13h48
  3. comportement de ifstream que je ne comprend pas
    Par Michaeljackfan dans le forum Langage
    Réponses: 5
    Dernier message: 19/09/2009, 14h21
  4. [thread][methodologie]Quelque chose que je ne comprends pas!
    Par norkius dans le forum Général Java
    Réponses: 5
    Dernier message: 16/03/2005, 14h01
  5. [Rave] un message que je ne comprends pas
    Par Clotilde dans le forum Rave
    Réponses: 2
    Dernier message: 30/09/2003, 21h46

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo