Variables Locales et variables Globales

Petites incompréhensions

a marqué ce sujet comme résolu.

Bonjour à tous,

J'ai débuté la programmation il y a quelques mois (Python).

J'ai commencé par le cours de LeGoff puis j'ai enchainé sur Swinnen. Je suis un brillant juriste ( ;) ) mais en programmation c'est une autre paire de manche …

Je suis bloqué depuis quelques jours sur un problème que je n'arrive pas à résoudre malgré mes recherches.

Il s'agit de remplacer par des caractères ( des petites * ) les lettres d'une liste.

Ex: Liste_non_etoilee ['a','b','c',d'] => Liste_etoilee = ['*','*','*','*'] Liste_non_etoilee est dans le corps de mon programme. C'est je pense ce que l'on appelle une variable Globale ( ?). Liste_non_etoilee est censée est un paramètre de la fonction mot_etoile

La première version de ma fonction, appelée par la suite dans le corps de mon programme était:

1
2
3
4
5
6
7
8
def mot_etoile (X):
    n=0
    a=1
    while a <= len (X):
        X [n] = '*'
        n +=1
        a+=1
    return X

Lors de l'utilisation de ma fonction dans le corps de mon programme j'écris donc MAVARIABLE = mot_etoile (Liste_non_etoilee) MAVARIABLE fait son job elle me renvoie ma liste avec les étoiles Sauf que ma variable Globale (?) Liste_non_etoilee me renvoie elle aussi des étoiles quand je la print …

J'ai cherché et j'ai trouvé une solution

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def mot_etoile (X):
    n=0
    a=1
    #création d'une liste vide pour éviter de changer X
    liste_sep = []
    for i in X:
        liste_sep.append (i)
    while a <= len (X):
        liste_sep [n] = '*'
        n +=1
        a+=1
    return liste_sep

Et la, formidable cela marche quand j'appelle ma fonction dans le corps elle me renvoie bien mes petites étoiles et ma variable globale (?) Liste_non_etoilee reste égale à elle même.

Sauf que je n'ai pas bien compris pourquoi. D'après ce que je sais :

  • Une variable globale ne peut être changée de l'intérieur d'une fonction
  • SAUF si on utilise une méthode sur cette variable au sein de la fonction -Or je n'ai pas utilisé de méthode ( ?) dans la première version de la fonction … Si ?

J'ai également esssayé une solution intermédiaire qui elle non plus ne marche pas puisque ma variable globale est aussi changée :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def mot_etoile (X):
    n=0
    a=1
    #Création d'une variable écran
    var_écran = X
    while a <= len (X):
        var_écran [n] = '*'
        n +=1
        a+=1
    return var_écran

Et la, c'est encore plus sombre, quand je teste ( print ( Liste_non_etoilee ) ) elle est étoiléé … Mais OUIALLE ? J'ai pourtant mis une variable "écran" ?

J'espère avoir été suffisamment clair, merci d'avance pour vos lumières …

Le X n'est pas une variable global. C'est un paramètre de ta fonction. Ce n'est pas ce phénomène qui rentre en ligne ici. Quand tu passe un objet en paramètre d'une fonction il donne une référence vers l'objet.

Donc quand tu fais :

1
2
toto = [1,2,3]
foo(toto)

ce que va avoir comme argument foo a l'intérieur de la fonction est la même liste. Donc si tu la modifie de l'intérieur, tu modifie celle qui a été passé.

Au passage ton code n'est pas du tout "pythonic". Que veux tu faire exactement ?

Techniquement ta fonction peut se résumer en :

1
2
def mot_etoile(X):
    return ["*"] * len(X)

Enfin en python on fait rarement des listes de caractères car tu peux accéder aux caractères d'une chaîne directement.

Merci beaucoup ta réponse.

En réalité j'essaie de faire un pendu, c'est pour cela que j'utilise des listes ( mutables ) au lieu de chaines.

Mais ma question est surtout algorithmique (?). J'ai relu ce que tu m'as écris avec attention, je ne suis pas très alaise encore avec "Quand tu passe un objet en paramètre d'une fonction il donne une référence vers l'objet." et je pense que c'est la clef de mon incompréhension.

Je comprends que X est un paramètre de ma fonction. Comme dans ta fonction. D'ailleurs je suis émerveillé par

1
    return ["*"] * len(X)

Je n'arrête pas de la lire et de me dire "évidement pourquoi n'y ai je pas pensé" ;)

Mais justement (peut être que je me trompe) je veux utiliser comme paramètre pour ma fonction une variable globale qui est d'après ce que j'ai compris une variable qui est présente dans le corps de mon programme et qui s'oppose aux variables dans le corps de mes fonctions qui sont locales.

Admettons que j'ai un programme où j'ai :

Var_glo = 'Jambon'

Mais aussi une fonction qui me fume mes aliments:

def Fumage ( Charcuterie): miam = enfume ma Charcuterie return miam

Quand je fais Fumage ( Saumon ) je me retrouve avec du Saumon_fumé ( super )

Mais :)

Quand je fais

def Fumage ( Jambon): miam = enfume ma Charcuterie return miam

et que j'appelle ma fonction dans mon programme j'ai bien en return Jambon_fumé. Sauf que lorsque j'appelle Jambon il est devenu AUSSI jambon_fumé ! Et je ne trouve pas cela 'logique' car je ne l'ai pas modifié !

Je vais relire un peu de doc python pour éclaircir ceci et revenir demain, car cela me trouble pas mal …

Euh… moi ca ne me pose pas de problème.

1
2
3
4
5
6
7
8
>>> def etoile(X):
...  return ["*"] * len(X)
... 
>>> pas_etoile=["a","b","c"]
>>> etoile(pas_etoile)
['*', '*', '*']
>>> pas_etoile
['a', 'b', 'c']

Et ce même en changeant la valeur de X dans la fonction :

1
2
3
4
5
6
7
8
9
>>> pas_etoile=["a","b","c"]
>>> def etoile(X):
...  X=["*"] * len(X)
...  return X
... 
>>> etoile(pas_etoile)
['*', '*', '*']
>>> pas_etoile
['a', 'b', 'c']

Est-ce qu'on pourrait avoir ton code complet, avec l'emplacement des print et tout :) ? Histoire de voir si y'a pas un

1
>>>pas_etoile=etoile(pas_etoile)

:-° C'est le genre de truc qui peut arriver :P .


Sinon en python tu peux très bien faire :

1
2
3
4
5
>>> mot="abc"
>>> mot[1]
'b'
>>> etoile(mot)
['*', '*', '*']

ou même modifier la fonction "etoile" et au lieu de

1
 return ["*"] * len(X)

mettre

1
 return "*" * len(X)

et tu aura une chaîne de caractère en sortie :)

+1 -1

Gwend@l : son problème ne vient pas de ma version mais de la sienne.

Bref, reprenons.

Avant tout ce que tu utilise Letoine ce ne sont pas des variables globales.

Petit exemple :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# a et c sont des variables définit dans l'ensemble du script
# C'est en général ce qu'on appel des variables globales
a = []
c = []

def foo():
    # Ma fonction utilise ici a comme une variable global
    # Elle la modifie directement !
    a.append(1)

# Avant l'execution de la fonction, a et c sont des listes vides
foo()
# maintenant a vaut [1] et c est toujours vide
# Pourquoi ? parce que la fonction modifie directement la variable global

# Autre fonction

def bar(taz):
    taz.append(42)

a = []
bar(a)
# maintenant a vaut [42]
# Pourquoi ?
# Ici aucune variable global en action. La fonction prend un paramètre.
# tout le problème viens du fait que la fonction le modifie directement
# Quand tu fais bar(a), Python ne copie pas "a" dans "taz" pour la fonction, il prend une référence.
# C'est a dire qu'il va dire "Ok en fait l'objet 'taz' ici c'est en réalité l'objet 'a'".
# Donc quand on fait le append() dans la fonction ça modifie 'a' puisque ce sont les même objets.

Il faut faire TRES attention avec les fonctions qui modifient les objets. On appel ça un effet de bord et c'est la source de nombreux bugs. Certains langages les interdisent !

Nous en Python on a le choix. Mais dans le doute tu devrais éviter : crée une nouvelle liste et c'est elle que tu modifie et renvoie.

Comprends tu mieux ?

+1 -0

Pour apporter quelques autres précisions :

  • Dans ton code initial, quand tu fais X[n] = '*', tu modifies la liste X (et tu utilises une méthode de X)
  • Quand tu assignes une variable existante à une autre variable, tu ne crées pas de nouvel objet. Tu peux mettre cela en évidence avec l'opérateur is.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
>>> a = []
>>> b = a
>>> a is b # a est le même objet que b
True
>>> a.append(0)
>>> a
[0]
>>> b
[0]
>>> b[0] = 1
>>> a
[1]
>>> c = [1]
>>> a is c # a et c ne sont pas le même objet
False
>>> a.append(2)
>>> a
[1, 2]
>>> c
[1]

Kje : En effet c'est son code qui est sensé ne pas marché, mais avec une version modifié de ton code, ou on change la valeur de X dans la fonction, je me retrouve quand même avec les 2 bonnes variables à la fin.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
def etoile1(X): # Version de Letoine
    n=0
    a=1
    while a <= len (X):
        X [n] = '*'
        n +=1
        a+=1
    return X

def etoile2(X): # Version de Kje, mais en modifiant la valeur de X dans la fonction
    X=['*'] * len(X)
    return X

pas_etoile1=['a','b','c']
pas_etoile2=['d','e','f']

print(etoile1(pas_etoile1)) # affiche ['*','*','*']
print(pas_etoile1)          # affiche ['*','*','*'], là ok je comprends pourquoi

print(etoile2(pas_etoile2)) # affiche ['*','*','*']
print(pas_etoile2)          # affiche ['d','e','f'], et là du coup je ne comprends plus

Les deux fonctions etoile1 et etoile2 modifient l'objet X, alors pourquoi la fonction etoile2 ne change pas la valeur de pas_etoile2 ?

+0 -0

Les deux fonctions etoile1 et etoile2 modifient l'objet X, alors pourquoi la fonction etoile2 ne change pas la valeur de pas_etoile2 ?

Gwend@l

Non. La fonction etoile2 ne modifie pas X. Elle réassigne une nouvelle valeur à la variable/étiquette X, rien de plus. J'ai parlé dans mon poste précédent de is, mais id est aussi bien utile pour comprendre ce genre d'opérations :

1
2
3
4
5
def etoile2(X):
    print(id(X))
    X = ['*'] * len(X)
    print(id(X)) # X n'est plus le même objet
    return X

Ok. Donc modifier un ou plus des éléments de X ne change pas X, par contre redéfinir X le change. C'est ça ?

Donc, si j'y bien compris :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def etoile(X):
    print(id(X))
    X[0]='1'
    print(id(X))      # renvoi le même id que le précédent
    X=['*'] * len(X)
    print(id(X))      # renvoi un id différent
    return X          

pas_etoile=['a','b','c']

print(etoile(pas_etoile))   # doit afficher ['*','*','*']
print(pas_etoile)           # doit afficher ['1','b','c']

Test en cours…

EDIT : Ca marche !! Merci Letoine ! J'ai compris des trucs :)

+0 -0

Un truc très important à comprendre en python est qu'un nom de variable est une étiquette vers un objet (une référence).

1
2
3
4
5
6
a=[1,2,3]
# L'étiquette a pointe sur une liste 
b=a
# les deux étiquettes pointent sur la même liste, le même objet. Les deux étiquettes sont donc synonyme
a=42
# Là on a changé l'étiquette "a" pour qu'elle pointe sur un nombre et non plus sur la liste. On a PAS modifié la liste et donc b pointe toujours sur la même liste. On a juste changé l'étiquette "a", pas le contenu vers quoi elle pointait avant

C'est un des gros problèmes des cours comme celui d'OC sur Python qui présentent la mémoire comme une grosse armoire et les variables comme des tiroirs : c'est faux en python. En Python les variables ne sont jamais que des post-it posé sur un tiroire, mais elles ne représentent pas le tiroir dans son ensemble.

Hmm pour un jeu du pendu, je crois pas que ça soit une bonne idée de bosser au niveau des octets, pour une raison très simple : l'encodage utf-8 représente les caractères non-ASCII sur plusieurs octets.

1
2
3
4
5
6
7
8
>>> len("Noël")
4
>>> len("Noël".encode())
5
>>> ''.join("*" for _ in "Noël")
'****'
>>> ''.join("*" for _ in "Noël".encode())
'*****'
+0 -0

Hmm pour un jeu du pendu, je crois pas que ça soit une bonne idée de bosser au niveau des octets, pour une raison très simple : l'encodage utf-8 représente les caractères non-ASCII sur plusieurs octets.

Oui, oui je sais bien, mais dans un jeu du pendu, il est rare de tester des caractères non ascii, même à la main (on ne dit pas je veux un é, mais un e) :)

1
2
>>> "e" == "ë"
False

Après s'il teste avec les codes non ascii, alors effectivement ce n'est pas le bon choix, mais c'est se compliquer la vie pour un simple jeu du pendu.

+0 -0

D'un point de vue purement pragmatique, tu as raison.

Par contre je crois vraiment que c'est une mauvaise chose de confondre octets et caractères. De mon point de vue, on ne devrait manipuler des bytes ou des bytearray que lorsque l'intention derrière est de manipuler des données binaires, brutes de décoffrage. À l'opposé, et c'est pour ça que Python a choisi de péter la rétrocompatibilité de façon aussi violente entre 2 et 3, le texte ne devrait être manipulé en tant que texte que sous forme de str unicode. C'est une chance que Python 3 représente tout son texte en unicode, contrairement à C par exemple qui dispose de deux types de caractères bien bâtards.

L'importance de faire cette distinction dès le début, c'est justement de ne pas se créer de problèmes à retardement. Oui, l'unicode peut être vraiment casse-tête à gérer correctement dans un programme, mais c'est une problématique importante en programmation aujourd'hui : le texte, ça a beau ne pas en avoir l'air, c'est une donnée riche et complexe. Si on commence par le confondre avec des octets (comme en C), d'une part on ne profite pas de tout ce que Python fait pour nous, mais en plus on finira forcément par tomber un beau jour sur un problème ultra-relou à résoudre, comme l'impossibilité de gérer un autre alphabet qu'ASCII, par exemple.

Si on prend le pendu : je préfère que le pendu le plus basique gère mal les inputs de lettres accentuées, mais qu'il les accepte sans fausser la longueur du mot, (quitte à rajouter plus tard du code pour faire en sorte que le e entré par l'utilisateur reconnaisse aussi éěēę dans le mot), plutôt que de planter au premier ç. Accessoirement, ça permet aussi de rendre le pendu jouable en japonais sans rien changer au code, juste parce qu'on utilise le type de données qui est fait pour représenter du texte.

C'est la sémantique avant tout qui doit guider le choix du type.

+1 -0

Me revoilà, après la lecture de vos posts et d'un peu (beaucoup) de doc!

@Kje :

J'ai bien compris ton petit exemple, et l'explication sur les étiquettes. Effectivement je ne pense pas que le cours d'OC (Le Goff) explique cette histoire de référence (mais j'ai pu la manquer).

J'ai trouvé dans "Learning Python 5ème édition" de Mark Lutz chez O'Reilly 3 shémas -p.179,180 et 181- qui sont plutôt explicites sur cette notion de référence. (mais je n'arrive pas à les copier j'éditerai si je trouve un moyen).

entwanne :

Merci pour les précisions. Effectivement is et id sont top pour vérifier tout cela :)

Gwend@ll :

Ok. Donc modifier un ou plus des éléments de X ne change pas X, par contre redéfinir X le change. C'est ça ?

Je garde ça en tête pour retenir les explications.

J'ai trouvé cette notion particulièrement complexe à comprendre, il va falloir que je m'entraine avant d'être alaise …

Merci à tous pour vos explications. Quand aux bitearray … Je lis mais je ne participe pas ce n'est pas de mon niveau ( encore ^^ )

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte