Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Question à deux sous

9 réponses
Avatar
Michel Claveau - MVP
Bonjour !


Soit ce code :

toto=1

def f1():
toto=2

def ff1():
print toto
#toto=3

ff1()

f1()


Il fonctionne.
Maintenant si on enlève le commentaire, on obtient :

toto=1

def f1():
toto=2

def ff1():
print toto
toto=3

ff1()

f1()

Et l'exécution donne un joli, et normal, TraceBack :
Traceback (most recent call last):
...
File "D:\Dev\python\win32\pywig\espacenom.py", line 7, in ff1
print toto
UnboundLocalError: local variable 'toto' referenced before assignment


Ma question : Suis-je légitime à utiliser cet exemple, pour (dé)montrer
que Python (du moins l'implémentation de Python que j'utilise) est
compilé, et non interprété ?


@+
--
Michel Claveau

9 réponses

Avatar
Michel Claveau - MVP
Re !

Tant que j'y suis, une question sémantique.
Dans le message :
UnboundLocalError: local variable 'toto' referenced before assignment

Je trouve les termes légèrement contradictoires, car 'bound' et
'assignment' sont traités comme des synonymes.

Ne serait-il pas préférable d'avoir :
UnboundLocalError: local variable 'toto' referenced before binding
ou
UnassignedLocalError: local variable 'toto' referenced before assignment

Bon, c'est un petit détail. Surtout que je parle pas anglais.
Et puis, ce n'est pas important.
Mais ça va me gâchee l'esprit jusqu'à l'apéro...

@+
--
Michel Claveau
Avatar
Alain Ketterlin
"Michel Claveau - MVP"
writes:

toto=1
def f1():
toto=2

def ff1():
print toto
toto=3

ff1()

f1()

Et l'exécution donne un joli, et normal, TraceBack :
Traceback (most recent call last):
...
File "D:Devpythonwin32pywigespacenom.py", line 7, in ff1
print toto
UnboundLocalError: local variable 'toto' referenced before assignment


Ma question : Suis-je légitime à utiliser cet exemple, pour (d é)montrer
que Python (du moins l'implémentation de Python que j'utilise) est
compilé, et non interprété ?



Je pense que cela n'a aucun rapport avec compilation/interprétation (si
tant est qu'il y ait une telle différence -- mais je ne sais pas ce que
tu entends par compilation).

Le problème vient de l'heuristique utilisée par l'analyseur pour décider
si une variable est locale (et donc masque les variables globales de
même nom) ou pas. La règle semble être : si la variable n'es t pas
déclarée "global" et si il y a une affectation à la variable dans un
bloc, alors elle est locale (indépendamment de la place de cette
affectation). Je tire cela de
http://docs.python.org/reference/simple_stmts.html#the-global-statement
et de ton exemple.

D'ailleurs, si tu as ajoutes "print toto" juste avant "toto=2" dans
f1(), tu as exactement le même message. Le fait de définir une fo nction
interne (ff1) ici ne change rien, c'est une affaire purement statique
(c'est-à-dire une caractéristique de l'analyseur : pour tout appr aition
de toto, va-t-on chercher sa valeur localement ou pas).

Dans ff1(), si il n'y a que "print toto", toto est la variable visible à  
cet endroit (celle qui vaut 2). Si tu ajoutes une affectation, elle
devient locale, et dans ce cas le "print" provoque l'erreur à
l'exécution.

Difficilement défendable, cela dit (euphémisme).

-- Alain.
Avatar
Alain Ketterlin
"Michel Claveau - MVP"
writes:

Tant que j'y suis, une question sémantique.
Dans le message :
UnboundLocalError: local variable 'toto' referenced before assignment

Je trouve les termes légèrement contradictoires, car 'bound' et
'assignment' sont traités comme des synonymes.



bound = liée, assignement = affectation

Un "assignment" est une façon de faire le "binding", mais il y en a
d'autres (par exemple le passage de paramètre).

Ne serait-il pas préférable d'avoir :
UnboundLocalError: local variable 'toto' referenced before binding
ou
UnassignedLocalError: local variable 'toto' referenced before assignment



Tu oublies "local" : puisque la variable est locale et qu'elle est
"unbound", ça veut dire qu'il manque un "assignement" (la seule forme de
binding possible pour une variable locale).

Le message est donc très précis, mais il fait l'hypothèse qu e la
variable est locale. Si le problème vient du fait que la variable ne
devrait pas être locale, alors tu peux ignorer la suite du message :-)

-- Alain.
Avatar
Alain BARTHE
A propos de variables locales/globales, il y a quelque chose que je n'ai
jamais vraiment bien compris en Python :

Dans le code ci-dessous :

w = 1
x = "toto"
y = []
z = {}

def f():
print "w = ", w
# w = w +1 => erreur

print "x = ", x
# x = x + "!" => erreur

print "y = ", y
y.append ("toto")
print "y = ", y

print "z = ", z
z ["x"] = "toto"
print "z = ", z

f()

1) Les variables w,x,y,z sont accessibles en "lecture" depuis la
fonction f, meme si elles ne sont pas declarees globales.

2) Les variables complexes (dictionnaire ou tableau) y et z semblent
modifiables meme si non declarees globales.

3) Par contre, w et x (respectivement entier et chaine)
ne sont modifiables que si declarees globales (exception levee dans le
cas contraire)

Je n'ai jamais su trouver dans la doc l'explication de cette difference
de comportement entre variables "simples et complexes"
Avatar
Alain Ketterlin
Alain BARTHE writes:

w = 1
x = "toto"
y = []
z = {}

def f():
print "w = ", w
# w = w +1 => erreur

print "x = ", x
# x = x + "!" => erreur

print "y = ", y
y.append ("toto") # ok

print "z = ", z
z ["x"] = "toto" # ok

f()

1) Les variables w,x,y,z sont accessibles en "lecture" depuis la
fonction f, meme si elles ne sont pas declarees globales.

2) Les variables complexes (dictionnaire ou tableau) y et z semblent
modifiables meme si non declarees globales.



Tu confonds variable (c'est-à-dire un nom) et objet. Toute la subtilit é
de ton example vient que l'opération s'applique, selon les cas, sur la
variable (pour w et x) ou sur l'objet référencé.

3) Par contre, w et x (respectivement entier et chaine)
ne sont modifiables que si declarees globales (exception levee dans le
cas contraire)
Je n'ai jamais su trouver dans la doc l'explication de cette
difference de comportement entre variables "simples et complexes"



La vraie distinction n'est pas sur le type, mais sur ce qu'on fait avec
la variable. Dans les cas w et x (int et chaine), tu changes la valeur
de la variable. Dans les cas y et z, tu ne changes pas la valeur de la
variable, mais celle de l'objet référencé (en clair : y a la même valeur
après y.append(...), dans le sens où elle désigne toujours l e même
objet, qui lui a changé). Pour z c'est pareil, c'est toujours le mà ªme
dict après l'opération. Donc w et x sont considérées co mme locales, et y
et z comme globales.

Donc ta distinction entre type simple et complexes n'est pertinente que
parce que "int" et "str" n'ont pas de méthode permettant de les modifi er
"sur place". Si il y avait une méthode de int, disons transmogrify(),
ayant un effet de bord, tu pourrais écrire w.transmogrify() sans
problème (par exemple, tu peux écrire w.__add__(3) sans problà ¨me, mais
ça ne change pas w).

Mais quand tu écris w=w+1 tu ne fais pas ça, mais :

1) tu calcules un nouvel objet (résultat de w+1)
2) tu changes l'objet référencé par w

On pourrait avoir envie de contester cette interprétation pour x=x+" !",
à cause de remarques dans la doc sur l'optimisation de + dans le cas
s=s+... (ou s+=...), mais ça ne change rien à l'affaire. Mà ªme si l'objet
est modifié sur place, la référence (la variable) est reaffe ctée. Donc,
elle devient locale si elle n'est pas déclarée globale.

-- Alain.
Avatar
Alain Ketterlin
Alain BARTHE writes:

w = 1
x = "toto"
y = []
z = {}

def f():
print "w = ", w
# w = w +1 => erreur

print "x = ", x
# x = x + "!" => erreur

print "y = ", y
y.append ("toto") # ok

print "z = ", z
z ["x"] = "toto" # ok

f()





Si j'ai bien compris y et z sont des objets "conteneurs" (tableau et
dictionnaire) dont la "référence" ne change pas, mais dont on p eut
modifier le "contenu", sans creer un nouvel objet.



C'est cela, mais, juste pour être précis : y et z ne sont pas des
objets, juste des noms qui désignent des objets. (Il pourrait d'ailleu rs
y avoir plusieurs noms pour le même objet. Et certains noms pourraient
être locaux et d'autres globaux. Par exemple :

l = [1,2]
def f():
l1 = l
l1.append(3)
print l

Deux noms, un objet.)

Pour les chaines le fonctionnement pourrait être identique, mais les
string python sont non modifiables par choix d'implementation.



Oui. D'ailleurs les objets importent peu ici (ils sont toujours
globalement accessibles, tant qu'on a un nom pour les désigner) : ce q ui
provoque les éventuelles erreurs sont les noms utilisés/affectà ©s.

De même, si x était une instance de classe X, on pourrait en mo difier
les attributs, sans modifier pour autant la variable qui référe nce
l'objet en question.

Par ex:
x = Personne ()

def f():
x.name = "toto"



Exactement (et aucune ambiguité sur name ici, puisqu'on sait où le
chercher).

-- Alain.
Avatar
eb303
On Apr 17, 3:18 pm, Alain Ketterlin
wrote:
Alain BARTHE writes:
>>> w = 1
>>> x = "toto"
>>> y = []
>>> z = {}

>>> def     f():
>>>         print "w = ", w
>>>         # w = w +1 => erreur

>>>         print "x = ", x
>>>         # x = x + "!" => erreur

>>>         print "y = ", y
>>>         y.append ("toto") # ok

>>>         print "z = ", z
>>>         z ["x"] = "toto" # ok

>>> f()
> Si j'ai bien compris y et z sont des objets "conteneurs" (tableau et
> dictionnaire) dont la "référence" ne change pas, mais dont on peut
> modifier le "contenu", sans creer un nouvel objet.

C'est cela, mais, juste pour être précis : y et z ne sont pas des
objets, juste des noms qui désignent des objets. (Il pourrait d'ailleur s
y avoir plusieurs noms pour le même objet. Et certains noms pourraient
être locaux et d'autres globaux. Par exemple :

l = [1,2]
def f():
    l1 = l
    l1.append(3)
    print l

Deux noms, un objet.)

> Pour les chaines le fonctionnement pourrait être identique, mais les
> string python sont non modifiables par choix d'implementation.

Oui. D'ailleurs les objets importent peu ici (ils sont toujours
globalement accessibles, tant qu'on a un nom pour les désigner) : ce qu i
provoque les éventuelles erreurs sont les noms utilisés/affectés.

> De même, si x était une instance de classe X, on pourrait en modifi er
> les attributs, sans modifier pour autant la variable qui référence
> l'objet en question.

> Par ex:
> x = Personne ()

> def f():
>    x.name = "toto"

Exactement (et aucune ambiguité sur name ici, puisqu'on sait où le
chercher).

-- Alain.



Tiens, encore plus rigolo:

def f(x, l):
x += 1
l += [1]

x, l = 0, []
f(x, l)
print x, l

Pas de problème global/local ici, mais on reste dans les problèmes de
bindings, de références et d'objets référencés. Je ne demande pas
d'explication, j'ai fini par comprendre pourquoi ça faisait ce que ça
fait. Mais c'est quand même assez troublant… Je subodore d'ailleurs
que c'est ce genre de cas qui a fait que les opérateurs += et al ont
mis si longtemps à être acceptés en Python.
Avatar
Alain Ketterlin
eb303 writes:

[...]
Tiens, encore plus rigolo:

def f(x, l):
x += 1
l += [1]

x, l = 0, []
f(x, l)
print x, l

Pas de problème global/local ici, mais on reste dans les problè mes de
bindings, de références et d'objets référencés.



Note que c'est exactement la même chose en Java, et que pour évit er cela
C++ a introduit les références, les copy-constructeurs, etc.
(c'est-à-dire : une foule de notions non-triviales). La stratégie
Python/Java/... est de loin la plus facile à mettre en oeuvre.

-- Alain.
Avatar
Alain Ketterlin
Bruno Desthuilliers writes:

Alain Ketterlin a écrit :
(snip)



[Je remets juste ce qu'il faut de contexte.]

> UnboundLocalError: local variable 'toto' referenced before assignment

Tu oublies "local" : puisque la variable est locale et qu'elle est
"unbound", ça veut dire qu'il manque un "assignement" (la seule for me de
binding possible pour une variable locale).



Les arguments de la fonctions sont également des variables locales,
donc il y a deux formes de binding possibles pour une locale !-)



Oui mais un argument ne peut pas être unbound il me semble.

-- Alain.