Visibilidade de variáveis ​​globais em módulos importados


112

Eu me deparei com uma espécie de parede importando módulos em um script Python. Farei o meu melhor para descrever o erro, por que o encontrei e por que estou vinculando essa abordagem específica para resolver meu problema (que descreverei em um segundo):

Vamos supor que eu tenha um módulo no qual defini algumas funções / classes de utilitários, que se referem a entidades definidas no namespace para o qual esse módulo auxiliar será importado (seja "a" essa entidade):

Módulo 1:

def f():
    print a

E então eu tenho o programa principal, onde "a" é definido, para o qual desejo importar esses utilitários:

import module1
a=3
module1.f()

Executar o programa irá desencadear o seguinte erro:

Traceback (most recent call last):
  File "Z:\Python\main.py", line 10, in <module>
    module1.f()
  File "Z:\Python\module1.py", line 3, in f
    print a
NameError: global name 'a' is not defined

Perguntas semelhantes foram feitas no passado (dois dias atrás, d'uh) e várias soluções foram sugeridas, no entanto, eu realmente não acho que elas atendam aos meus requisitos. Este é meu contexto particular:

Estou tentando fazer um programa Python que se conecta a um servidor de banco de dados MySQL e exibe / modifica dados com uma GUI. Para fins de limpeza, defini o grupo de funções auxiliares / utilitárias relacionadas ao MySQL em um arquivo separado. No entanto, todos eles têm uma variável comum, que eu defini originalmente dentro do módulo de utilitários, e que é o objeto cursor do módulo MySQLdb. Mais tarde, percebi que o objeto cursor (que é usado para se comunicar com o servidor db) deve ser definido no módulo principal, de forma que tanto o módulo principal quanto qualquer coisa importada para ele possam acessar esse objeto.

O resultado final seria algo como este:

utilities_module.py:

def utility_1(args):
    code which references a variable named "cur"
def utility_n(args):
    etcetera

E meu módulo principal:

program.py:

import MySQLdb, Tkinter
db=MySQLdb.connect(#blahblah) ; cur=db.cursor()  #cur is defined!
from utilities_module import *

E então, assim que tento chamar qualquer uma das funções de utilitários, ele aciona o erro "nome global não definido" mencionado anteriormente.

Uma sugestão particular era ter uma instrução "from program import cur" no arquivo de utilitários, como este:

utilities_module.py:

from program import cur
#rest of function definitions

program.py:

import Tkinter, MySQLdb
db=MySQLdb.connect(#blahblah) ; cur=db.cursor()  #cur is defined!
from utilities_module import *

Mas isso é importação cíclica ou algo parecido e, no final das contas, também trava. Então, minha pergunta é:

Como diabos posso tornar o objeto "cur", definido no módulo principal, visível para as funções auxiliares que são importadas para ele?

Obrigado pelo seu tempo e minhas sinceras desculpas se a solução foi postada em outro lugar. Simplesmente não consigo encontrar a resposta sozinho e não tenho mais truques em meu livro.


1
Com base em sua atualização: você provavelmente não deseja um único cursor compartilhado de qualquer maneira. Uma única conexão compartilhada , sim, mas os cursores são baratos, e muitas vezes há boas razões para ter vários cursores ativos ao mesmo tempo (por exemplo, para que você possa iterar por meio de dois deles em um único passo em vez de ter que fetch_alliterar por meio de duas listas em vez disso , ou apenas para que você possa ter dois threads / greenlets / callback-chains / qualquer outro usando o banco de dados sem conflitos).
abarnert

De qualquer forma, o que você deseja compartilhar, eu acho que a resposta aqui é mover-se db(e cur, se você insistir) em um módulo separado que tanto programe utilities_moduleimportá-lo a partir. Dessa forma, você não obtém dependências circulares (importar programa de módulos que o programa importa) e a confusão que vem com eles.
abarnert de

Respostas:


244

Globais em Python são globais para um módulo , não em todos os módulos. (Muitas pessoas ficam confusas com isso, porque em, digamos, C, um global é o mesmo em todos os arquivos de implementação, a menos que você o faça explicitamente static.)

Existem diferentes maneiras de resolver isso, dependendo do seu caso de uso real.


Antes mesmo de trilhar esse caminho, pergunte-se se isso realmente precisa ser global. Talvez você realmente queira uma classe, com fum método de instância, em vez de apenas uma função livre? Então você poderia fazer algo assim:

import module1
thingy1 = module1.Thingy(a=3)
thingy1.f()

Se você realmente deseja um global, mas ele está lá apenas para ser usado por module1, defina-o nesse módulo.

import module1
module1.a=3
module1.f()

Por outro lado, se afor compartilhado por vários módulos, coloque-o em outro lugar e faça com que todos importem:

import shared_stuff
import module1
shared_stuff.a = 3
module1.f()

… E, em module1.py:

import shared_stuff
def f():
    print shared_stuff.a

Não use uma fromimportação a menos que a variável pretenda ser uma constante. from shared_stuff import acriaria uma nova avariável inicializada com qualquer shared_stuff.areferência no momento da importação, e essa nova avariável não seria afetada pelas atribuições a shared_stuff.a.


Ou, no caso raro de você realmente precisar que seja realmente global em todos os lugares, como um integrado, adicione-o ao módulo integrado. Os detalhes exatos diferem entre Python 2.xe 3.x. No 3.x, funciona assim:

import builtins
import module1
builtins.a = 3
module1.f()

12
Agradável e abrangente.
kindall

Obrigado pela sua resposta. Vou tentar a abordagem import shared_stuff, embora não consiga superar o fato de que as variáveis ​​definidas no módulo principal não são realmente globais - Não há nenhuma maneira de tornar um nome verdadeiramente acessível a todos, de qualquer parte do programa ?
Nubarke

1
Ter algo ser "acessível a todos, de qualquer programa" vai contra o zen do python , especificamente "Explícito é melhor do que implícito". Python tem um design oo muito bem pensado e, se você usá-lo bem, pode conseguir seguir o resto de sua carreira em python sem precisar usar a palavra-chave global novamente.
Bi Rico

1
ATUALIZAÇÃO: OBRIGADO! A abordagem shared_stuff funcionou bem, com isso acho que posso contornar qualquer outro problema.
Nubarke

1
@DanielArmengod: Eu adicionei a resposta embutida, caso você realmente precise. (E se você precisar de mais detalhes, pesquise SO; há pelo menos duas questões sobre como adicionar coisas aos builtins apropriadamente.) Mas, como Bi Rico diz, você quase certamente não precisa ou quer.
abarnert

9

Como solução alternativa, você pode considerar a configuração de variáveis ​​de ambiente na camada externa, como esta.

main.py:

import os
os.environ['MYVAL'] = str(myintvariable)

mymodule.py:

import os

myval = None
if 'MYVAL' in os.environ:
    myval = os.environ['MYVAL']

Como precaução extra, trate do caso quando MYVAL não estiver definido dentro do módulo.


5

Uma função usa os globais do módulo em que está definida . Em vez de definir a = 3, por exemplo, você deve definir module1.a = 3. Portanto, se você deseja curdisponibilizar como um em global utilities_module, defina utilities_module.cur.

Uma solução melhor: não use globais. Passe as variáveis ​​de que você precisa para as funções que precisam delas ou crie uma classe para agrupar todos os dados e passe-as ao inicializar a instância.


Em vez de escrever 'import module1', se o usuário tivesse escrito 'from module1 import f', então f viria no namespace global de main.py. Agora, em main.py, se usarmos f (), como a = 3 e f (definição de função) estão ambos no namespace global de main. Isso é uma solução? Se eu estiver errado, por favor, você pode me direcionar para algum artigo sobre este assunto
variável de

Usei a abordagem acima e passei os globais como argumentos ao instanciar as classes que estão usando os globais. Isso foi OK, mas algum tempo depois ativamos uma verificação de código Sonarqube do código e isso reclamou que as funções têm muitos argumentos. Portanto, tivemos que procurar outra solução. Agora usamos variáveis ​​de ambiente que são lidas por cada módulo que precisa delas. Não é realmente compatível com OOP, mas é isso. Isso funciona apenas quando os globais não mudam durante a execução do código.
rimetnac

5

Este post é apenas uma observação sobre o comportamento do Python que encontrei. Talvez os conselhos que você leu acima não funcionem para você se você fez a mesma coisa que fiz abaixo.

Ou seja, tenho um módulo que contém variáveis ​​globais / compartilhadas (conforme sugerido acima):

#sharedstuff.py

globaltimes_randomnode=[]
globalist_randomnode=[]

Então eu tive o módulo principal que importa o material compartilhado com:

import sharedstuff as shared

e alguns outros módulos que realmente preencheram esses arrays. Eles são chamados pelo módulo principal. Ao sair desses outros módulos, posso ver claramente que os arrays estão preenchidos. Mas ao lê-los no módulo principal, eles estavam vazios. Isso foi muito estranho para mim (bem, eu sou novo em Python). No entanto, quando eu mudo a forma como eu importo o sharedstuff.py no módulo principal para:

from globals import *

funcionou (os arrays foram preenchidos).

Apenas dizendo'


2

A solução mais fácil para este problema específico teria sido adicionar outra função dentro do módulo que teria armazenado o cursor em uma variável global para o módulo. Então, todas as outras funções também poderiam usá-lo.

Módulo 1:

cursor = None

def setCursor(cur):
    global cursor
    cursor = cur

def method(some, args):
    global cursor
    do_stuff(cursor, some, args)

programa principal:

import module1

cursor = get_a_cursor()
module1.setCursor(cursor)
module1.method()

2

Como os globais são específicos do módulo, você pode adicionar a seguinte função a todos os módulos importados e usá-la para:

  • Adicione variáveis ​​singulares (no formato de dicionário) como globais para aqueles
  • Transfira seus principais módulos globais para ele.

addglobals = lambda x: globals (). update (x)

Então, tudo que você precisa para passar os globais atuais é:

módulo de importação

module.addglobals (globals ())


1

Como não o vi nas respostas acima, pensei em adicionar minha solução alternativa simples, que é apenas adicionar um global_dictargumento à função que requer os globais do módulo de chamada e, em seguida, passar o dict para a função ao chamar; por exemplo:

# external_module
def imported_function(global_dict=None):
    print(global_dict["a"])


# calling_module
a = 12
from external_module import imported_function
imported_function(global_dict=globals())

>>> 12

0

A maneira OOP de fazer isso seria tornar seu módulo uma classe em vez de um conjunto de métodos não acoplados. Em seguida, você pode usar __init__ou um método setter para definir as variáveis ​​do chamador para uso nos métodos do módulo.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.