Visibilitas variabel global dalam modul yang diimpor

112

Saya telah mengalami sedikit modul pengimpor dinding dalam skrip Python. Saya akan melakukan yang terbaik untuk menjelaskan kesalahan tersebut, mengapa saya mengalaminya, dan mengapa saya menggunakan pendekatan khusus ini untuk menyelesaikan masalah saya (yang akan saya jelaskan sebentar lagi):

Misalkan saya memiliki modul di mana saya telah mendefinisikan beberapa fungsi / kelas utilitas, yang merujuk ke entitas yang ditentukan dalam namespace tempat modul tambahan ini akan diimpor (misalkan "a" menjadi entitas seperti itu):

modul 1:

def f():
    print a

Dan kemudian saya memiliki program utama, di mana "a" didefinisikan, di mana saya ingin mengimpor utilitas tersebut:

import module1
a=3
module1.f()

Menjalankan program akan memicu kesalahan berikut:

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

Pertanyaan serupa telah diajukan di masa lalu (dua hari yang lalu, d'uh) dan beberapa solusi telah disarankan, namun menurut saya ini tidak sesuai dengan kebutuhan saya. Inilah konteks khusus saya:

Saya mencoba membuat program Python yang terhubung ke server database MySQL dan menampilkan / memodifikasi data dengan GUI. Demi kebersihan, saya telah mendefinisikan sekumpulan fungsi bantu / utilitas terkait MySQL dalam file terpisah. Namun mereka semua memiliki variabel umum, yang awalnya saya tentukan di dalam modul utilitas, dan yang merupakan objek kursor dari modul MySQLdb. Saya kemudian menyadari bahwa objek kursor (yang digunakan untuk berkomunikasi dengan server db) harus didefinisikan dalam modul utama, sehingga modul utama dan apa pun yang diimpor ke dalamnya dapat mengakses objek itu.

Hasil akhirnya akan seperti ini:

utilities_module.py:

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

Dan modul utama saya:

program.py:

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

Dan kemudian, segera setelah saya mencoba memanggil salah satu fungsi utilitas, ini memicu kesalahan "nama global tidak ditentukan" yang disebutkan di atas.

Saran khusus adalah memiliki pernyataan "from program import cur" di file utilitas, seperti ini:

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 *

Tapi itu impor siklik atau sesuatu seperti itu dan, intinya, itu macet juga. Jadi pertanyaan saya adalah:

Bagaimana saya bisa membuat objek "cur", yang didefinisikan dalam modul utama, terlihat oleh fungsi tambahan yang diimpor ke dalamnya?

Terima kasih atas waktu Anda dan permintaan maaf saya yang terdalam jika solusinya telah diposting di tempat lain. Saya tidak bisa menemukan jawabannya sendiri dan saya tidak punya trik lagi di buku saya.

Nubarke
sumber
1
Berdasarkan pembaruan Anda: Anda mungkin tidak menginginkan satu pun kursor bersama. Satu koneksi bersama , ya, tetapi kursor itu murah, dan sering kali ada alasan bagus untuk memiliki beberapa kursor yang hidup pada saat yang sama (misalnya, sehingga Anda dapat beralih melalui dua di antaranya secara berurutan alih-alih harus fetch_alldan beralih melalui dua daftar sebagai gantinya , atau agar Anda dapat memiliki dua utas / greenlets / callback-chains / apa pun yang berbeda menggunakan database tanpa konflik).
abarnert
Bagaimanapun, apa pun yang ingin Anda bagikan, saya pikir jawabannya di sini adalah untuk memindahkan db(dan cur, jika Anda bersikeras) ke modul terpisah yang keduanya programdan utilities_moduleimpor darinya. Dengan cara itu Anda tidak mendapatkan dependensi melingkar (mengimpor program dari modul yang diimpor oleh program) dan kebingungan yang menyertainya.
abarnert

Jawaban:

244

Global di Python bersifat global untuk satu modul , bukan di semua modul. (Banyak orang bingung dengan ini, karena di, katakanlah, C, global adalah sama di semua file implementasi kecuali Anda secara eksplisit membuatnya static.)

Ada berbagai cara untuk mengatasinya, bergantung pada kasus penggunaan Anda yang sebenarnya.


Bahkan sebelum menempuh jalur ini, tanyakan pada diri Anda apakah ini benar-benar perlu global. Mungkin Anda benar-benar menginginkan kelas, dengan fsebagai metode contoh, daripada hanya fungsi gratis? Kemudian Anda bisa melakukan sesuatu seperti ini:

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

Jika Anda benar-benar menginginkan global, tetapi itu hanya untuk digunakan oleh module1, setel di modul itu.

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

Di sisi lain, jika adibagikan oleh banyak modul, letakkan di tempat lain, dan minta semua orang mengimpornya:

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

… Dan, dalam module1.py:

import shared_stuff
def f():
    print shared_stuff.a

Jangan gunakan fromimpor kecuali variabel dimaksudkan sebagai konstanta. from shared_stuff import aakan membuat avariabel baru yang diinisialisasi ke apa pun yang shared_stuff.adirujuk pada saat impor, dan avariabel baru ini tidak akan terpengaruh oleh tugas ke shared_stuff.a.


Atau, dalam kasus yang jarang terjadi di mana Anda benar-benar membutuhkannya untuk benar-benar global di mana-mana, seperti bawaan, tambahkan ke modul bawaan. Detail persisnya berbeda antara Python 2.x dan 3.x. Dalam 3.x, ini berfungsi seperti ini:

import builtins
import module1
builtins.a = 3
module1.f()
abarnert
sumber
12
Bagus dan lengkap.
kindall
Terima kasih atas jawaban anda. Saya akan mencoba pendekatan import shared_stuff, meskipun saya tidak dapat melupakan fakta bahwa variabel yang ditentukan dalam modul utama tidak benar - benar global - Apakah tidak ada cara untuk membuat nama benar-benar dapat diakses oleh semua orang, dari mana pun di program ?
Nubarke
1
Memiliki sesuatu yang "dapat diakses oleh semua orang, dari program apa pun" sangat bertentangan dengan zen python , khususnya "Eksplisit lebih baik daripada implisit". Python memiliki desain yang dipikirkan dengan sangat baik, dan jika Anda menggunakannya dengan baik, Anda mungkin berhasil menjalani sisa karir python Anda tanpa perlu menggunakan kata kunci global lagi.
Bi Rico
1
UPDATE: TERIMA KASIH! Pendekatan shared_stuff bekerja dengan baik, dengan ini saya rasa saya dapat mengatasi masalah lainnya.
Nubarke
1
@DanielArmengod: Saya menambahkan jawaban bawaan, jika Anda benar-benar membutuhkannya. (Dan jika Anda membutuhkan lebih banyak detail, cari SO; setidaknya ada dua pertanyaan tentang bagaimana menambahkan barang ke built-in dengan benar.) Tapi, seperti yang dikatakan Bi Rico, Anda hampir pasti tidak benar-benar membutuhkannya, atau menginginkannya.
abarnert
9

Sebagai solusinya, Anda dapat mempertimbangkan untuk mengatur variabel lingkungan di lapisan luar, seperti ini.

main.py:

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

mymodule.py:

import os

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

Sebagai tindakan pencegahan ekstra, tangani kasus ketika MYVAL tidak didefinisikan di dalam modul.

Vishal
sumber
5

Sebuah fungsi menggunakan global dari modul yang ditentukan di dalamnya . Sebagai a = 3contoh, Anda harus menyetel daripada menyetel module1.a = 3. Jadi, jika Anda ingin curtersedia sebagai global in utilities_module, set utilities_module.cur.

Solusi yang lebih baik: jangan gunakan global. Teruskan variabel yang Anda perlukan ke dalam fungsi yang membutuhkannya, atau buat kelas untuk menggabungkan semua data menjadi satu, dan teruskan saat menginisialisasi instance.

kindall
sumber
Alih-alih menulis 'import module1', jika pengguna telah menulis 'from module1 import f', maka f akan muncul di namespace global main.py. Sekarang di main.py jika kita menggunakan f (), maka karena a = 3 dan f (definisi fungsi) keduanya dalam globalnamespace dari main. Apakah ini solusinya? Jika saya salah, tolong dapatkah Anda mengarahkan saya ke artikel apa pun tentang subjek ini
variabel
Saya menggunakan pendekatan di atas dan meneruskan global sebagai argumen saat membuat instance kelas yang menggunakan global. Ini baik-baik saja, tetapi beberapa saat kemudian kami telah mengaktifkan pemeriksaan kode Sonarqube dari kode tersebut dan ini mengeluhkan bahwa fungsi memiliki terlalu banyak argumen. Jadi kami harus mencari solusi lain. Sekarang kami menggunakan variabel lingkungan yang dibaca oleh setiap modul yang membutuhkannya. Ini tidak benar-benar sesuai dengan OOP tapi hanya itu. Ini hanya berfungsi jika global tidak berubah selama eksekusi kode.
rimetnac
5

Posting ini hanyalah observasi untuk perilaku Python yang saya temui. Mungkin saran yang Anda baca di atas tidak berhasil untuk Anda jika Anda melakukan hal yang sama seperti yang saya lakukan di bawah.

Yaitu, saya memiliki modul yang berisi variabel global / bersama (seperti yang disarankan di atas):

#sharedstuff.py

globaltimes_randomnode=[]
globalist_randomnode=[]

Lalu saya memiliki modul utama yang mengimpor barang-barang yang dibagikan dengan:

import sharedstuff as shared

dan beberapa modul lain yang benar-benar mengisi larik ini. Ini disebut oleh modul utama. Saat keluar dari modul lain ini, saya dapat dengan jelas melihat bahwa array sudah terisi. Tapi saat membacanya kembali di modul utama, semuanya kosong. Ini agak aneh bagi saya (yah, saya baru mengenal Python). Namun, ketika saya mengubah cara saya mengimpor sharedstuff.py di modul utama menjadi:

from globals import *

itu berhasil (array diisi).

Katakan saja

pengguna3336548
sumber
2

Solusi termudah untuk masalah khusus ini adalah dengan menambahkan fungsi lain di dalam modul yang akan menyimpan kursor dalam variabel global ke modul. Kemudian semua fungsi lainnya dapat menggunakannya juga.

modul 1:

cursor = None

def setCursor(cur):
    global cursor
    cursor = cur

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

program utama:

import module1

cursor = get_a_cursor()
module1.setCursor(cursor)
module1.method()
Chris Nasr
sumber
2

Karena global adalah modul khusus, Anda dapat menambahkan fungsi berikut ke semua modul yang diimpor, lalu menggunakannya untuk:

  • Tambahkan variabel tunggal (dalam format kamus) sebagai variabel global untuk itu
  • Transfer global modul utama Anda ke sana.

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

Maka yang Anda butuhkan untuk menyampaikan global saat ini adalah:

modul impor

module.addglobals (global ())

pengguna2589273
sumber
1

Karena saya belum melihatnya dalam jawaban di atas, saya pikir saya akan menambahkan solusi sederhana saya, yang hanya menambahkan global_dictargumen ke fungsi yang membutuhkan global modul pemanggil, dan kemudian meneruskan dict ke dalam fungsi saat memanggil; misalnya:

# 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
Toby Petty
sumber
0

Cara OOP untuk melakukan ini adalah membuat modul Anda menjadi kelas, bukan kumpulan metode tak terikat. Kemudian Anda dapat menggunakan __init__atau metode penyetel untuk menyetel variabel dari pemanggil untuk digunakan dalam metode modul.

coyot
sumber