Comment créer une variable cross-module?

122

La __debug__variable est pratique en partie parce qu'elle affecte chaque module. Si je veux créer une autre variable qui fonctionne de la même manière, comment le ferais-je?

La variable (soyons original et appelons-la «foo») n'a pas besoin d'être vraiment globale, en ce sens que si je change foo dans un module, elle est mise à jour dans les autres. Ce serait bien si je pouvais définir foo avant d'importer d'autres modules et qu'ils verraient la même valeur pour cela.

Dan Homerick
la source

Réponses:

114

Je n'approuve en aucune manière cette solution, ni sous aucune forme ni sous aucune forme. Mais si vous ajoutez une variable au __builtin__module, elle sera accessible comme si une variable globale de n'importe quel autre module qui inclut __builtin__- qui est tous, par défaut.

a.py contient

print foo

b.py contient

import __builtin__
__builtin__.foo = 1
import a

Le résultat est que "1" est imprimé.

Edit: Le __builtin__module est disponible en tant que symbole local __builtins__- c'est la raison de l'écart entre deux de ces réponses. Notez également qu'il __builtin__a été renommé builtinsen python3.

Curt Hagenlocher
la source
2
Une raison pour laquelle vous n'aimez pas cette situation?
Software Enthusiastic
31
D'une part, cela brise les attentes des gens lorsqu'ils lisent du code. "Quel est ce symbole 'foo' utilisé ici? Pourquoi ne puis-je pas voir où il est défini?"
Curt Hagenlocher
9
Il est également susceptible de faire des ravages si une future version de Python commence à utiliser le nom que vous avez choisi comme élément intégré.
intuité
4
C'est une bonne solution pour des choses comme le partage d'une connexion db avec des modules importés. Pour vérifier la cohérence, je m'assure que le module importé confirme hasattr(__builtin__, "foo").
Mike Ellis
4
Pour quiconque lit cette réponse: NE PAS! FAIRE ! CE ! Vraiment, non.
bruno desthuilliers
161

Si vous avez besoin d'une variable globale inter-module, peut-être qu'une simple variable globale au niveau du module suffira.

a.py:

var = 1

b.py:

import a
print a.var
import c
print a.var

c.py:

import a
a.var = 2

Tester:

$ python b.py
# -> 1 2

Exemple du monde réel: global_settings.py de Django (bien que dans les applications Django, les paramètres soient utilisés en important l' objet django.conf.settings ).

jfs
la source
3
Mieux car cela évite les conflits d'espace de noms possibles
bgw
Que faire si le module que vous importez, dans ce cas a.py, contient main()? Est-ce que ça importe?
samedi
4
@sedeh: non. Si a.py est également exécuté en tant que script, utilisez if __name__=="__main__"guard pour éviter d'exécuter du code inattendu lors de l'importation.
jfs
6
Dans le monde réel, vous devez être un peu prudent avec cette solution. Si un programmeur récupère votre variable «globale» en utilisant «à partir d'une variable d'importation», (essayez cette variante de c.py), il obtient une copie de la variable au moment de l'importation.
Paul Whipp
1
@PaulWhipp: faux (indice: utiliser id()pour vérifier l'identité)
jfs
25

Je crois qu'il existe de nombreuses circonstances dans lesquelles cela a du sens et cela simplifie la programmation d'avoir des globaux connus à travers plusieurs modules (étroitement couplés). Dans cet esprit, je voudrais développer un peu l'idée d'avoir un module de globals qui est importé par les modules qui ont besoin de les référencer.

Quand il n'y a qu'un seul module de ce type, je le nomme "g". Dans celui-ci, j'attribue des valeurs par défaut pour chaque variable que j'ai l'intention de traiter comme globale. Dans chaque module qui utilise l'un d'entre eux, je n'utilise pas "from g import var", car il en résulte uniquement une variable locale qui n'est initialisée à partir de g qu'au moment de l'importation. Je fais la plupart des références sous la forme g.var, et le "g." me rappelle constamment que j'ai affaire à une variable potentiellement accessible à d'autres modules.

Si la valeur d'une telle variable globale doit être utilisée fréquemment dans une fonction d'un module, alors cette fonction peut faire une copie locale: var = g.var. Cependant, il est important de comprendre que les affectations à var sont locales et que g.var global ne peut pas être mis à jour sans référencer explicitement g.var dans une affectation.

Notez que vous pouvez également avoir plusieurs modules globaux de ce type partagés par différents sous-ensembles de vos modules pour garder les choses un peu plus étroitement contrôlées. La raison pour laquelle j'utilise des noms courts pour mes modules globaux est d'éviter de trop encombrer le code avec leurs occurrences. Avec seulement un peu d'expérience, ils deviennent assez mnémotechniques avec seulement 1 ou 2 caractères.

Il est toujours possible de faire une assignation à, disons, gx quand x n'était pas déjà défini dans g, et un module différent peut alors accéder à gx Cependant, même si l'interpréteur le permet, cette approche n'est pas si transparente, et j'évite il. Il existe toujours la possibilité de créer accidentellement une nouvelle variable dans g à la suite d'une faute de frappe dans le nom de la variable pour une affectation. Parfois, un examen de dir (g) est utile pour découvrir les noms de surprise qui auraient pu survenir par un tel accident.

David Vanderschel
la source
7
Cette observation intéressante a résolu mon problème: "Je n'utilise pas" from g import var ", car cela ne produit qu'une variable locale qui est initialisée à partir de g uniquement au moment de l'importation." Il semble raisonnable de supposer que "from..import" équivaut à "import" mais ce n'est pas vrai.
Curtis Yallop
24

Définissez un module (appelez-le "globalbaz") et définissez les variables à l'intérieur. Tous les modules utilisant ce "pseudoglobal" doivent importer le module "globalbaz", et s'y référer en utilisant "globalbaz.var_name"

Cela fonctionne quel que soit le lieu de la modification, vous pouvez modifier la variable avant ou après l'importation. Le module importé utilisera la dernière valeur. (J'ai testé cela dans un exemple de jouet)

Pour plus de précision, globalbaz.py ressemble à ceci:

var_name = "my_useful_string"
Hayalci
la source
9

Vous pouvez transmettre les globaux d'un module à un autre:

Dans le module A:

import module_b
my_var=2
module_b.do_something_with_my_globals(globals())
print my_var

Dans le module B:

def do_something_with_my_globals(glob): # glob is simply a dict.
    glob["my_var"]=3
user394430
la source
7

Les variables globales sont généralement une mauvaise idée, mais vous pouvez le faire en attribuant à __builtins__:

__builtins__.foo = 'something'
print foo

De plus, les modules eux-mêmes sont des variables auxquelles vous pouvez accéder à partir de n'importe quel module. Donc, si vous définissez un module appelé my_globals.py:

# my_globals.py
foo = 'something'

Ensuite, vous pouvez également l'utiliser de n'importe où:

import my_globals
print my_globals.foo

Utiliser des modules plutôt que de les modifier __builtins__est généralement une manière plus propre de faire des globaux de ce genre.

spiv
la source
3
__builtins__est une particularité de CPython, vous ne devriez vraiment pas l'utiliser - meilleure utilisation __builtin__(ou builtinsen Python3) comme le montre la réponse acceptée
Tobias Kienzler
5

Vous pouvez déjà le faire avec des variables au niveau du module. Les modules sont les mêmes quel que soit le module à partir duquel ils sont importés. Ainsi, vous pouvez faire de la variable une variable de niveau module dans n'importe quel module dans lequel il est judicieux de la placer, et y accéder ou lui affecter à partir d'autres modules. Il serait préférable d'appeler une fonction pour définir la valeur de la variable ou d'en faire une propriété d'un objet singleton. De cette façon, si vous finissez par avoir besoin d'exécuter du code lorsque la variable est modifiée, vous pouvez le faire sans casser l'interface externe de votre module.

Ce n'est généralement pas une excellente façon de faire les choses - l'utilisation de globaux l'est rarement - mais je pense que c'est la façon la plus propre de le faire.

intuité
la source
3

Je voulais poster une réponse qu'il y a un cas où la variable ne sera pas trouvée.

Les importations cycliques peuvent interrompre le comportement du module.

Par exemple:

first.py

import second
var = 1

second.py

import first
print(first.var)  # will throw an error because the order of execution happens before var gets declared.

main.py

import first

Sur cet exemple, cela devrait être évident, mais dans une grande base de code, cela peut être vraiment déroutant.

Jonathan
la source
1

Cela ressemble à la modification de l' __builtin__espace de nom. Pour le faire:

import __builtin__
__builtin__.foo = 'some-value'

N'utilisez pas __builtins__directement (notez les "s" supplémentaires) - apparemment cela peut être un dictionnaire ou un module. Merci à ΤΖΩΤΖΙΟΥ de l'avoir signalé, vous en trouverez plus ici .

Maintenant fooest disponible pour une utilisation partout.

Je ne recommande pas de faire cela en général, mais l'utilisation de cela dépend du programmeur.

L'attribution doit être effectuée comme ci-dessus, le réglage foo = 'some-other-value'ne le définira que dans l'espace de noms actuel.

awatts
la source
1
Je me souviens (de comp.lang.python) que l'utilisation directe de builtins doit être évitée; à la place, l' importation builtin et l' utilisation qui, comme le suggère Curt Hagenlocher.
tzot le
1

J'utilise ceci pour quelques fonctions primitives intégrées qui me manquaient vraiment. Un exemple est une fonction de recherche qui a la même sémantique d'utilisation que filtre, mappage, réduction.

def builtin_find(f, x, d=None):
    for i in x:
        if f(i):
            return i
    return d

import __builtin__
__builtin__.find = builtin_find

Une fois que cela est exécuté (par exemple, en important près de votre point d'entrée), tous vos modules peuvent utiliser find () comme si, évidemment, il était intégré.

find(lambda i: i < 0, [1, 3, 0, -5, -10])  # Yields -5, the first negative.

Remarque: vous pouvez le faire, bien sûr, avec un filtre et une autre ligne pour tester la longueur nulle, ou avec une réduction dans une sorte de ligne étrange, mais j'ai toujours pensé que c'était bizarre.

Brian Arsuaga
la source
1

Je pourrais obtenir des variables modifiables (ou mutables ) inter-modules en utilisant un dictionnaire:

# in myapp.__init__
Timeouts = {} # cross-modules global mutable variables for testing purpose
Timeouts['WAIT_APP_UP_IN_SECONDS'] = 60

# in myapp.mod1
from myapp import Timeouts

def wait_app_up(project_name, port):
    # wait for app until Timeouts['WAIT_APP_UP_IN_SECONDS']
    # ...

# in myapp.test.test_mod1
from myapp import Timeouts

def test_wait_app_up_fail(self):
    timeout_bak = Timeouts['WAIT_APP_UP_IN_SECONDS']
    Timeouts['WAIT_APP_UP_IN_SECONDS'] = 3
    with self.assertRaises(hlp.TimeoutException) as cm:
        wait_app_up(PROJECT_NAME, PROJECT_PORT)
    self.assertEqual("Timeout while waiting for App to start", str(cm.exception))
    Timeouts['WAIT_JENKINS_UP_TIMEOUT_IN_SECONDS'] = timeout_bak

Lors du lancement test_wait_app_up_fail, la durée réelle du timeout est de 3 secondes.

foudfou
la source
1

Je me suis demandé s'il serait possible d'éviter certains des inconvénients liés à l'utilisation de variables globales (voir par exemple http://wiki.c2.com/?GlobalVariablesAreBad ) en utilisant un espace de noms de classe plutôt qu'un espace de noms global / module pour passer des valeurs de variables . Le code suivant indique que les deux méthodes sont essentiellement identiques. Il y a un léger avantage à utiliser les espaces de noms de classe comme expliqué ci-dessous.

Les fragments de code suivants montrent également que des attributs ou des variables peuvent être créés et supprimés dynamiquement dans les espaces de noms globaux / module et les espaces de noms de classe.

wall.py

# Note no definition of global variables

class router:
    """ Empty class """

J'appelle ce module «mur» car il est utilisé pour rebondir des variables sur. Il agira comme un espace pour définir temporairement les variables globales et les attributs à l'échelle de la classe de la classe vide 'router'.

source.py

import wall
def sourcefn():
    msg = 'Hello world!'
    wall.msg = msg
    wall.router.msg = msg

Ce module importe le mur et définit une fonction unique sourcefnqui définit un message et l'émet par deux mécanismes différents, l'un via les globaux et l'autre via la fonction routeur. Notez que les variables wall.msget wall.router.messagesont définies ici pour la première fois dans leurs espaces de noms respectifs.

dest.py

import wall
def destfn():

    if hasattr(wall, 'msg'):
        print 'global: ' + wall.msg
        del wall.msg
    else:
        print 'global: ' + 'no message'

    if hasattr(wall.router, 'msg'):
        print 'router: ' + wall.router.msg
        del wall.router.msg
    else:
        print 'router: ' + 'no message'

Ce module définit une fonction destfnqui utilise les deux mécanismes différents pour recevoir les messages émis par la source. Cela permet la possibilité que la variable «msg» n'existe pas. destfnsupprime également les variables une fois qu'elles ont été affichées.

main.py

import source, dest

source.sourcefn()

dest.destfn() # variables deleted after this call
dest.destfn()

Ce module appelle les fonctions précédemment définies dans l'ordre. Après le premier appel aux dest.destfnvariables wall.msget wall.router.msgn'existent plus.

La sortie du programme est:

global: Bonjour tout le monde!
routeur: Bonjour tout le monde!
global: pas de
routeur de message : pas de message

Les fragments de code ci-dessus montrent que les mécanismes de variable module / global et classe / classe sont essentiellement identiques.

Si beaucoup de variables doivent être partagées, la pollution de l'espace de noms peut être gérée soit en utilisant plusieurs modules de type mur, par exemple wall1, wall2 etc., soit en définissant plusieurs classes de type routeur dans un seul fichier. Ce dernier est légèrement plus ordonné, donc représente peut-être un avantage marginal pour l'utilisation du mécanisme de variable de classe.

robertofbaycot
la source