namespace et variable vagabonde
#1

Voici un problème que vous pouvez avoir rencontré sans le comprendre ou sans vous en apercevoir.
Il est susceptible de créer des conflits de variables entre plusieurs scripts, mais peut aussi bien n'avoir aucune conséquence visible bien qu'étant quand même là.


Commençons par créer un array nommé testvar dans le namespace global.
tcl
.tcl array set testvar {1 a 2 b}
 Tcl:


Nous allons maintenant tenter de créer une variable du même nom dans la déclaration du namespace testnamespace.
tcl
.tcl namespace eval ::testnamespace { set testvar 1 }
Tcl error: can't set "testvar": variable is array


Plutôt curieux, on a tenté de créer la variable testvar depuis l'intérieur du namespace testnamespace mais il semble que Tcl veuille la créer dans le namespace global à la place. Par conséquent, nous obtenons une erreur car on ne peut pas redéfinir une variable de type array de cette façon.



Reproduisons maintenant le test mais en incluant le code dans une procédure.
tcl
.tcl namespace eval ::testnamespace { proc testproc {} { set testvar 1 } }
Tcl:


puis exécutons ladite procédure
tcl
.tcl ::testnamespace::testproc
Tcl: 1


Là, plus d'erreur



tcl
.tcl info exists ::testnamespace::testvar
Tcl: 0


On peut constater accessoirement que la variable n'existe déjà plus.
En effet, set utilisé dans une procédure crée une variable temporaire... à moins qu'elle n'ait été déclarée par variable auparavant :
tcl
.tcl namespace eval ::testnamespace { proc testproc {} { variable testvar ; set testvar 1 } }
Tcl:


ou plus simplement
tcl
.tcl namespace eval ::testnamespace { proc testproc {} { variable testvar 1 } }
Tcl:
.tcl ::testnamespace::testproc
Tcl:
.tcl info exists ::testnamespace::testvar
Tcl: 1



_____________________


Que conclure de tout ça ?
Le seul cas où vous devriez utiliser namespace eval est pour "déclarer" le namespace et initialiser vos variables au moyen de la commande variable.

En dehors de ces variables proprement déclarées, il est plus sûr de définir vos autres variables en dehors du namespace eval (juste au cas où il existerait une variable globale du même nom).

Il faut savoir que lorsque vous définissez une variable au moyen de set sans l'avoir au préalable déclarée avec variable, Tcl recherche la variable dans le namespace local, ne la trouve pas (puisqu'elle n'est pas déclarée), puis décide qu'il s'agit d'une variable globale (située dans le namespace global).
Par conséquent, ce n'est pas la variable $testnamespace::testvar qui est créée, mais bien $::testvar.

Pour éviter d'"égarer" vos variables dans le namespace global et ainsi risquer un conflit avec une autre variable qui porterait le même nom, voici la bonne façon de procéder :
tcl
.tcl namespace eval ::testnamespace { variable testvar }
Tcl:
.tcl proc ::testnamespace::testproc {} { set testvar 1 }
Tcl:
.tcl ::testnamespace::testproc
Tcl: 1



Une bonne habitude à prendre est de définir et d'appeler vos variables en précisant systématiquement leur namespace, ça élimine tout risque de confusion. L'exemple qui suit peut donc se passer de l'initialisation de la variable :
tcl
.tcl namespace eval testnamespace {}
Tcl: 
.tcl set ::testnamespace::testvar 1
Tcl: 1
.tcl info exists ::testnamespace::testvar
Tcl: 1




Voici qui peut être pratique pour traquer la création de variables globales, et donc détecter d'éventuels problèmes de "fuites" de variables dans un script : Watching Global Variables Creation for Memory Leak Detection


Source : Namespace confusion

  Répondre   Avertir
#2
Bonsoir.

Après discution avec Menz pour bien comprendre le problème, je trouve ca logique.

En effet il faut bien différencier les 2 fonctions :
"variable vartest" qui déclare une variable dans le namespace courant
"set vartest 1" qui affecte une valeur a une variable


Ainsi de la même facon qu'il est utile d'appeler des variables globales dans un namespace, par exemple :

tcl
namespace eval testnamespace { putlog "$version" }



il n'est pas illogique de pouvoir les manipuler en écriture :

tcl
.tcl namespace eval testnamespace { set version 3}




Dans les 2 cas, le bot va voir qu'aucune variable "version" n'a été déclarée au niveau du namespace courant et donc regarder au niveau du namespace global.

Le problème n'est donc pas la portabilité de la variable mais bien la déclaration implicite des variables par la fonction "set" lorsque celles-ci n'existent pas qui nous facilite trop la vie et empêche d'être vigilant sur la pré-existence de certaines variables au niveau global.


Je suppose que comme d'habitude je ne suis pas clair mais Menz saura j'en suis sûr expliquer mon point de vue ; en tout cas, je ne trouve ce comportement tout à fait normal.

Après, l'accessibilité et la modification de n'importe qu'elle variable de n'importe quel namespace depuis n'importe quel namespace me parait incohérent et dangereux mais il parait que ca fait partie de la philosophie des eggdrop...



PS : la ligne
tcl
".tcl namespace eval testnamespace { set version 3} "


ne fonctionne pas car la variable version est protégée mais c'était pour que tout le monde comprenne l'exemple (ou pas)
  Répondre   Avertir
#3
^ tout comme Galdinx il a dit.

TCL à tendance à faire tout un tas de "raccourcis" dont on est pas toujours conscients, et qui peuvent casser des trucs dans des cas comme ça.

J'ajoute aussi que le même genre de comportement existe aussi pour les procédures:
si la procédure ::salut::lol apelle la procédure "truc", et que la procéudre ::salut::truc existe, celle là sera lancée en priorité... et sinon, ::truc.
  Répondre   Avertir
#4
Ca ne me choque pas d'avoir un tcl dans lequel le namespace est fermé puis ré-ouvert. Ca permet de bien séparer les parties et ça ne provoque pas d'erreur.
Si on fait un énorme tcl qui peut être scindé en plusieurs fichiers (structure "modulaire" ?), on redéclarera le namespace et ça ne choquera personne, donc pourquoi ce serait génant si c'est pareil au sein du même fichier ?
  Répondre   Avertir
#5
Voila, c'est bien une question de préférence, ce n'est pas une raison technique.
Regarde chanrelay, je fais exactement ce que tu n'aimes pas, juste pour séparer la partie configuration de la partie "active".
Et pire encore, 2 fonctions du namespace (init et deinit) sont déclarées hors du namespace.
  Répondre   Avertir


Atteindre :


Utilisateur(s) parcourant ce sujet : 1 visiteur(s)