web-dev-qa-db-fra.com

Comment créer des variables de module en Python?

Est-il possible de configurer une variable globale à l'intérieur d'un module? Lorsque j'ai essayé de le faire de la manière la plus évidente, comme indiqué ci-dessous, l'interprète Python a déclaré que la variable __DBNAME__ n'existait pas.

...
__DBNAME__ = None

def initDB(name):
    if not __DBNAME__:
        __DBNAME__ = name
    else:
        raise RuntimeError("Database name has already been set.")
...

Et après avoir importé le module dans un autre fichier

...
import mymodule
mymodule.initDB('mydb.sqlite')
...

Et la trace était: UnboundLocalError: local variable '__DBNAME__' referenced before assignment

Des idées? J'essaie de configurer un singleton à l'aide d'un module, conformément à la recommandation de celle de ce type .

192
daveslab

Voici ce qui se passe.

Premièrement, les seules variables globales que Python a réellement sont des variables à portée de module. Vous ne pouvez pas créer une variable qui soit vraiment globale. tout ce que vous pouvez faire est de créer une variable dans une étendue particulière. (Si vous créez une variable dans l'interpréteur Python, puis importez d'autres modules, votre variable se trouve dans la portée la plus externe et est donc globale dans votre session Python.)

Tout ce que vous avez à faire pour créer une variable globale est simplement d’affecter un nom.

Imaginez un fichier appelé foo.py, contenant cette seule ligne:

X = 1

Maintenant, imaginez que vous l'importez.

import foo
print(foo.X)  # prints 1

Cependant, supposons que vous souhaitiez utiliser l'une de vos variables de module-scope en tant que global dans une fonction, comme dans votre exemple. Par défaut, Python suppose que les variables de fonction sont locales. Vous ajoutez simplement une déclaration global dans votre fonction avant d'essayer d'utiliser la variable globale.

def initDB(name):
    global __DBNAME__  # add this line!
    if __DBNAME__ is None: # see notes below; explicit test for None
        __DBNAME__ = name
    else:
        raise RuntimeError("Database name has already been set.")

En passant, pour cet exemple, le simple test if not __DBNAME__ est adéquat, car toute valeur de chaîne autre qu'une chaîne vide sera évaluée comme telle, ainsi tout nom de base de données réel sera évalué comme tel. Mais pour les variables pouvant contenir une valeur numérique pouvant être 0, vous ne pouvez pas simplement dire if not variablename; dans ce cas, vous devez explicitement tester None à l'aide de l'opérateur is. J'ai modifié l'exemple pour ajouter un test explicite None. Le test explicite de None n'est jamais faux, je l'utilise par défaut.

Enfin, comme d'autres l'ont noté sur cette page, deux signaux de soulignement soulignés indiquent à Python que vous souhaitez que la variable soit "privée" du module. Si vous faites un import * from mymodule, Python n'importera pas de noms comportant deux traits de soulignement dans votre espace de noms. Mais si vous faites simplement un import mymodule et que vous dites ensuite dir(mymodule), vous verrez les variables "privées" dans la liste, et si vous faites explicitement référence à mymodule.__DBNAME__ Python s'en foutra, il vous laissera juste vous y référer. Les soulignements en double interligne sont un indice important pour les utilisateurs de votre module que vous ne souhaitez pas qu'ils associent ce nom à une valeur qui leur est propre.

Il est considéré comme la meilleure pratique dans Python de ne pas faire import *, mais de minimiser le couplage et de maximiser le caractère explicite en utilisant mymodule.something ou en effectuant explicitement une importation comme from mymodule import something .

EDIT: Si, pour une raison quelconque, vous avez besoin de faire quelque chose comme ceci dans une très ancienne version de Python qui ne contient pas le mot clé global, il existe une solution de contournement simple. Au lieu de définir directement une variable globale de module, utilisez un type mutable au niveau global du module et stockez vos valeurs à l'intérieur.

Dans vos fonctions, le nom de la variable globale sera en lecture seule; vous ne pourrez pas relier le nom de la variable globale réelle. (Si vous affectez ce nom de variable à l'intérieur de votre fonction, cela affectera uniquement le nom de la variable locale à l'intérieur de la fonction.) Mais vous pouvez utiliser ce nom de variable locale pour accéder à l'objet global réel et y stocker des données.

Vous pouvez utiliser un list mais votre code sera laid:

__DBNAME__ = [None] # use length-1 list as a mutable

# later, in code:  
if __DBNAME__[0] is None:
    __DBNAME__[0] = name

Un dict est meilleur. Mais le plus pratique est une instance de classe, et vous pouvez simplement utiliser une classe triviale:

class Box:
    pass

__m = Box()  # m will contain all module-level values
__m.dbname = None  # database name global in module

# later, in code:
if __m.dbname is None:
    __m.dbname = name

(Il n'est pas nécessaire de capitaliser la variable du nom de la base de données.)

J'aime le sucre syntaxique de simplement utiliser __m.dbname plutôt que __m["DBNAME"]; cela semble la solution la plus pratique à mon avis. Mais la solution dict fonctionne bien aussi.

Avec dict, vous pouvez utiliser n’importe quelle valeur hashable comme clé, mais lorsque vous êtes satisfait des noms qui sont des identificateurs valides, vous pouvez utiliser une classe triviale telle que Box dans ce qui précède.

220
steveha

Accès explicite aux variables de niveau module en y accédant de manière explicite sur le module


En bref: La technique décrite ici est identique à celle utilisée dans réponse de steveha , à l'exception de , aucun objet d'assistance artificiel n'est créé pour définir explicitement les variables. Au lieu de cela, l'objet module se voit attribuer un pointeur de variable et fournit donc une portée explicite sur l'accès de partout. (comme les assignations dans la portée de la fonction locale) .

Pensez-y comme self pour le module actuel au lieu de l'instance actuelle!

# db.py
import sys

# this is a pointer to the module object instance itself.
this = sys.modules[__name__]

# we can explicitly make assignments on it 
this.db_name = None

def initialize_db(name):
    if (this.db_name is None):
        # also in local function scope. no scope specifier like global is needed
        this.db_name = name
        # also the name remains free for local use
        db_name = "Locally scoped db_name variable. Doesn't do anything here."
    else:
        msg = "Database is already initialized to {0}."
        raise RuntimeError(msg.format(this.db_name))

Les modules étant mis en cache et par conséquent importés une seule fois , vous pouvez importer db.py autant de fois que vous le souhaitez sur autant de clients que vous le souhaitez, en manipulant le même état universel:

# client_a.py
import db

db.initialize_db('mongo')
# client_b.py
import db

if (db.db_name == 'mongo'):
    db.db_name = None  # this is the preferred way of usage, as it updates the value for all clients, because they access the same reference from the same module object
# client_c.py
from db import db_name
# be careful when importing like this, as a new reference "db_name" will
# be created in the module namespace of client_c, which points to the value 
# that "db.db_name" has at import time of "client_c".

if (db_name == 'mongo'):  # checking is fine if "db.db_name" doesn't change
    db_name = None  # be careful, because this only assigns the reference client_c.db_name to a new value, but leaves db.db_name pointing to its current value.

En tant que bonus supplémentaire, je le trouve assez globalement Pythonic, car cela convient parfaitement à la politique Pythons de . Explicit vaut mieux qu'implicite .

60
timmwagener

La réponse de Steveha m'a été utile, mais a omis un point important (celui sur lequel je pense que Wisty voulait en venir). Le mot clé global n'est pas nécessaire si vous accédez uniquement à la variable mais ne l'attribuez pas dans la fonction.

Si vous affectez la variable sans le mot-clé global, alors Python crée une nouvelle variable locale - la valeur de la variable de module sera désormais masquée dans la fonction. Utilisez le mot-clé global pour affecter la variable de module dans une fonction.

Pylint 1.3.1 sous Python 2.7 impose de ne PAS utiliser global si vous n'affectez pas la var.

module_var = '/dev/hello'

def readonly_access():
    connect(module_var)

def readwrite_access():
    global module_var
    module_var = '/dev/hello2'
    connect(module_var)
24
Brad Dre

Pour cela, vous devez déclarer la variable comme globale. Cependant, une variable globale est également accessible à partir de extérieur du module en utilisant module_name.var_name. Ajoutez ceci comme première ligne de votre module:

global __DBNAME__
6
Chinmay Kanchi