Видимость глобальных переменных в импортированных модулях

113

Я столкнулся с проблемой импорта модулей в скрипт Python. Я сделаю все возможное, чтобы описать ошибку, почему я столкнулся с ней и почему я привязываю именно этот подход к решению моей проблемы (которую я опишу через секунду):

Предположим, у меня есть модуль, в котором я определил некоторые служебные функции / классы, которые относятся к сущностям, определенным в пространстве имен, в которое будет импортирован этот вспомогательный модуль (пусть «a» будет такой сущностью):

модуль1:

def f():
    print a

И затем у меня есть основная программа, в которой определено "a", в которую я хочу импортировать эти утилиты:

import module1
a=3
module1.f()

Запуск программы вызовет следующую ошибку:

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

Подобные вопросы задавались и раньше (два дня назад, дах), и было предложено несколько решений, однако я действительно не думаю, что они соответствуют моим требованиям. Вот мой конкретный контекст:

Я пытаюсь создать программу Python, которая подключается к серверу базы данных MySQL и отображает / изменяет данные с помощью графического интерфейса. Для чистоты я выделил кучу вспомогательных / служебных функций, связанных с MySQL, в отдельный файл. Однако все они имеют общую переменную, которую я изначально определил внутри модуля утилит, и которая является объектом курсора из модуля MySQLdb. Позже я понял, что объект курсора (который используется для связи с сервером db) должен быть определен в основном модуле, чтобы и основной модуль, и все, что в него импортируется, могли получить доступ к этому объекту.

Конечный результат будет примерно таким:

utilities_module.py:

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

И мой основной модуль:

program.py:

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

И затем, как только я пытаюсь вызвать любую из функций утилит, возникает вышеупомянутая ошибка «глобальное имя не определено».

В частности, предлагалось включить в файл утилит оператор "from program import cur", например:

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 *

Но это циклический импорт или что-то в этом роде, и, в конечном итоге, он тоже вылетает. Итак, мой вопрос:

Как, черт возьми, я могу сделать объект "cur", определенный в основном модуле, видимым для тех вспомогательных функций, которые в него импортированы?

Спасибо за ваше время и мои глубочайшие извинения, если решение было размещено в другом месте. Я просто не могу найти ответ сам, да и в моей книге больше нет трюков.

Нубарке
источник
1
Основываясь на вашем обновлении: вам, вероятно, все равно не нужен единый общий курсор. Одно общее соединение , да, но курсоры дешевы, и часто есть веские причины для одновременного использования нескольких курсоров (например, чтобы вы могли перебирать два из них синхронно, вместо того, чтобы fetch_allперебирать два списка. или просто так, что у вас может быть два разных потока / гринлета / цепочки обратных вызовов / что угодно, использующее базу данных без конфликтов).
abarnert
В любом случае, чем бы вы ни хотели поделиться, я думаю, что ответ здесь состоит в том, чтобы переместить dbcur, если вы настаиваете) в отдельный модуль, из которого programи utilities_moduleимпортировать его. Таким образом, вы не получите циклических зависимостей (импорт программы из модулей, которые программа импортирует) и путаницы, которая с ними связана.
abarnert

Ответы:

245

Глобальные переменные в Python являются глобальными для модуля , а не для всех модулей. (Многих это сбивает с толку, потому что, скажем, в C глобальное значение одинаково для всех файлов реализации, если вы явно не укажете его static.)

Есть разные способы решить эту проблему в зависимости от вашего фактического использования.


Прежде чем даже пойти по этому пути, спросите себя, действительно ли это должно быть глобальным. Может быть, вам действительно нужен класс с fметодом экземпляра, а не просто бесплатная функция? Тогда вы могли бы сделать что-то вроде этого:

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

Если вам действительно нужен global, но он нужен только для использования module1, установите его в этом модуле.

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

С другой стороны, если aон используется множеством модулей, поместите его в другое место и попросите всех импортировать его:

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

… И в module1.py:

import shared_stuff
def f():
    print shared_stuff.a

Не используйте fromимпорт, если переменная не должна быть константой. from shared_stuff import aсоздаст новую aпеременную, инициализированную для всего, что shared_stuff.aупоминается во время импорта, и эта новая aпеременная не будет затронута присваиваниями shared_stuff.a.


Или, в редком случае, когда вам действительно нужно, чтобы он был действительно глобальным везде, например, встроенный, добавьте его во встроенный модуль. Точные детали отличаются между Python 2.x и 3.x. В 3.x это работает так:

import builtins
import module1
builtins.a = 3
module1.f()
Abarnert
источник
12
Красиво и всесторонне.
kindall
Спасибо за Ваш ответ. Я попробую использовать подход import shared_stuff, хотя я не могу смириться с тем фактом, что переменные, определенные в основном модуле, на самом деле не глобальные - нет ли способа сделать имя действительно доступным для всех, из любой точки программы ?
Nubarke
1
Наличие чего-то «доступного для всех из любой программы» во многом противоречит дзену Python , в частности, «Явное лучше, чем неявное». Python имеет очень хорошо продуманную конструкцию, и если вы будете правильно его использовать, вам, возможно, удастся продолжить свою карьеру на Python без необходимости повторного использования ключевого слова global.
Bi Rico
1
ОБНОВЛЕНИЕ: СПАСИБО! Подход shared_stuff работал отлично, с этим, я думаю, я могу обойти любые другие проблемы.
Nubarke
1
@DanielArmengod: Я добавил встроенный ответ, на случай, если он вам действительно нужен. (И если вам нужно больше деталей, ищите SO; есть как минимум два вопроса о том, как правильно добавлять что-то во встроенные функции.) Но, как говорит Би Рико, вам почти наверняка это не нужно, или оно вам не нужно.
abarnert
9

В качестве обходного пути вы можете рассмотреть возможность установки переменных среды на внешнем уровне, например, здесь.

main.py:

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

mymodule.py:

import os

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

В качестве дополнительной меры предосторожности обработайте случай, когда MYVAL не определен внутри модуля.

Vishal
источник
5

Функция использует глобальные переменные модуля, в котором она определена . a = 3Например, вместо настройки вы должны выполнять настройку module1.a = 3. Итак, если вы хотите, чтобы он был curдоступен как глобальный utilities_module, установите utilities_module.cur.

Лучшее решение: не используйте глобальные переменные. Передайте нужные вам переменные в функции, которые в них нуждаются, или создайте класс, чтобы связать все данные вместе и передать его при инициализации экземпляра.

любезный
источник
Вместо записи «import module1», если бы пользователь написал «from module1 import f», тогда f попал бы в глобальное пространство имен main.py. Теперь в main.py, если мы используем f (), то, поскольку a = 3 и f (определение функции) находятся в глобальном пространстве имен main. Это решение? Если я ошибаюсь, не могли бы вы направить меня к любой статье на эту тему, пожалуйста
переменная
Я использовал описанный выше подход и передал глобальные переменные в качестве аргументов при создании экземпляров классов, которые используют глобальные переменные. Это было нормально, но через некоторое время мы активировали проверку кода Sonarqube, и он пожаловался на то, что функции имеют слишком много аргументов. Поэтому пришлось искать другое решение. Теперь мы используем переменные среды, которые считываются каждым модулем, который в них нуждается. Это не совсем совместимо с ООП, но это все. Это работает, только если глобальные переменные не меняются во время выполнения кода.
rimetnac
5

Этот пост - просто наблюдение за поведением Python, с которым я столкнулся. Возможно, советы, которые вы прочитали выше, не сработают для вас, если вы сделаете то же, что и я.

А именно, у меня есть модуль, который содержит глобальные / общие переменные (как было предложено выше):

#sharedstuff.py

globaltimes_randomnode=[]
globalist_randomnode=[]

Затем у меня был основной модуль, который импортирует общие материалы с помощью:

import sharedstuff as shared

и некоторые другие модули, которые фактически заполняли эти массивы. Они вызываются основным модулем. При выходе из этих других модулей я ясно вижу, что массивы заполнены. Но при чтении их в основном модуле они были пустыми. Для меня это было довольно странно (ну, я новичок в Python). Однако, когда я меняю способ импорта sharedstuff.py в основной модуль, чтобы:

from globals import *

это сработало (массивы были заполнены).

Просто говорю'

user3336548
источник
2

Самым простым решением этой конкретной проблемы было бы добавление другой функции в модуль, которая сохраняла бы курсор в переменной, глобальной для модуля. Тогда все остальные функции тоже смогут его использовать.

модуль1:

cursor = None

def setCursor(cur):
    global cursor
    cursor = cur

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

основная программа:

import module1

cursor = get_a_cursor()
module1.setCursor(cursor)
module1.method()
Крис Наср
источник
2

Поскольку глобальные переменные зависят от модуля, вы можете добавить следующую функцию ко всем импортированным модулям, а затем использовать ее для:

  • Добавьте единичные переменные (в формате словаря) в качестве глобальных для тех
  • Перенесите в него глобальные объекты основного модуля.

addglobals = лямбда x: globals (). update (x)

Тогда все, что вам нужно передать текущим глобальным переменным, это:

модуль импорта

module.addglobals (globals ())

user2589273
источник
1

Поскольку я не видел этого в ответах выше, я подумал, что добавлю свой простой обходной путь, который заключается в global_dictтом, чтобы просто добавить аргумент в функцию, требующую глобальных переменных вызывающего модуля, а затем передать dict в функцию при вызове; например:

# 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

ООП способ сделать это - сделать ваш модуль классом, а не набором несвязанных методов. Затем вы можете использовать __init__метод или метод установки, чтобы установить переменные вызывающего объекта для использования в методах модуля.

койот
источник