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


112

Я наткнувся на трохи модулів, що імпортують стіни в сценарії 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

Подібні запитання були задані в минулому (два дні тому, d'uh), і було запропоновано кілька рішень, однак я не думаю, що вони відповідають моїм вимогам. Ось мій конкретний контекст:

Я намагаюся зробити програму Python, яка підключається до сервера баз даних MySQL і відображає / модифікує дані за допомогою GUI. Для чистоти я визначив купу допоміжних / корисних функцій 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 cur import import", наприклад такий:

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повторити і повторити два списки , або просто так, ви можете мати дві різні нитки / greenlets / callback-ланцюги / що б там не було, використовуючи базу даних без конфліктів).
abarnert

У будь-якому випадку, що б ви не хотіли поділитися, я думаю, що відповідь тут - перенести dbcur, якщо ви наполягаєте) на окремий модуль, який обидва programі utilities_moduleімпортувати з нього. Таким чином, ви не отримуєте кругових залежностей (імпорт програми з модулів, які програмують імпорт) та плутанини, що виникає з ними.
abarnert

Відповіді:


244

Глобали в Python є глобальними для модуля , не для всіх модулів. (Багатьох людей бентежить це, тому що, скажімо, C, глобальна версія є однаковою для всіх файлів реалізації, якщо ви прямо не вкажете цього static.)

Існують різні способи вирішити це, залежно від конкретного випадку використання.


Перш ніж навіть піти по цьому шляху, запитайте себе, чи справді це має бути глобальним. Можливо, ви дійсно хочете клас, fяк метод екземпляра, а не просто вільну функцію? Тоді ви могли б зробити щось подібне:

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

Якщо ви дійсно хочете глобальний, але це просто там, до якого слід скористатися 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()

12
Приємно і всебічно.
kindall

Дякую за вашу відповідь. Я спробую імпорту share_stuff підходу, хоча я не можу подолати той факт, що змінні, визначені в головному модулі, насправді не є глобальними - чи не існує жодного способу зробити ім’я справді доступним для всіх, звідки б не було в програмі ?
Нубарке

1
Маючи щось "доступне для всіх, з будь-якої програми", це дуже суперечить дзену пітона , зокрема "Явне краще, ніж неявне". У Python є дуже продуманий дизайн дизайну, і якщо ви його добре використовуєте, вам, можливо, вдасться пройти решту вашої кар'єри пітона, не потребуючи знову використовувати глобальне ключове слово.
Бі-Ріко

1
ОНОВЛЕННЯ: ДЯКУЄМО! Підхід shared_stuff спрацював чудово, тому я думаю, що я можу вирішити будь-які інші проблеми.
Нубарке

1
@DanielArmengod: Я додав вбудовану відповідь, на випадок, якщо вона вам справді потрібна. (І якщо вам потрібні додаткові деталі, знайдіть ТАК; є як мінімум два питання, як правильно додати речі до вбудованих елементів.) Але, як каже Бі-Ріко, ви майже точно не потребуєте цього чи хочете.
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 не визначений всередині модуля.


5

Функція використовує глобальні дані модуля, в якому він визначений . Замість цього a = 3, наприклад, слід налаштувати module1.a = 3. Тож, якщо ви хочете, щоб він був curдоступний як глобальний utilities_module, встановіть utilities_module.cur.

Краще рішення: не використовуйте глобали. Передайте потрібні змінні функції, які потребують, або створіть клас, щоб поєднати всі дані разом, і передайте їх при ініціалізації екземпляра.


Замість того, щоб написати "імпорт модуля1", якби користувач написав "з модуля1 імпорту 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 *

він працював (масиви були заселені).

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


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 (глобальні ())


1

Оскільки я не бачив цього у відповідях вище, я думав, що додам простий спосіб вирішення, який полягає лише в тому, щоб додати global_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

Способом OOP це було б зробити ваш модуль класом замість набору незв'язаних методів. Тоді ви можете використовувати __init__або метод setter для встановлення змінних від абонента для використання в модульних методах.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.