การมองเห็นตัวแปรส่วนกลางในโมดูลที่นำเข้า


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

มีการถามคำถามที่คล้ายกันในอดีต (เมื่อสองวันก่อน 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 *

จากนั้นทันทีที่ฉันพยายามเรียกใช้ฟังก์ชันยูทิลิตี้ใด ๆ มันจะทำให้เกิดข้อผิดพลาด "global name not defined" ดังกล่าวข้างต้น

ข้อเสนอแนะเฉพาะคือให้มีคำสั่ง "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

อย่างไรก็ตามสิ่งที่คุณต้องการแบ่งปันฉันคิดว่าคำตอบที่นี่คือการย้ายdb(และcurถ้าคุณยืนยัน) ไปยังโมดูลแยกต่างหากที่ทั้งสองprogramและutilities_moduleนำเข้าจาก ด้วยวิธีนี้คุณจะไม่ได้รับการอ้างอิงแบบวงกลม (การนำเข้าโปรแกรมจากโมดูลที่โปรแกรมนำเข้า) และความสับสนที่มาพร้อมกับพวกเขา
abarnert

คำตอบ:


245

Globals ใน Python เป็นโมดูลทั่วโลกไม่ใช่ในทุกโมดูล (หลายคนสับสนในเรื่องนี้เพราะใน C พูดว่า global เหมือนกันในทุกไฟล์การนำไปใช้งานเว้นแต่คุณจะระบุไว้อย่างชัดเจน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เรียกว่าในช่วงเวลาของการนำเข้าและใหม่นี้ตัวแปรจะไม่ได้รับผลกระทบจากการที่ได้รับมอบหมายไปashared_stuff.a


หรือในกรณีที่เกิดขึ้นได้ยากที่คุณจำเป็นต้องใช้งานได้ทั่วโลกอย่างแท้จริงเช่นในตัวให้เพิ่มลงในโมดูลในตัว รายละเอียดที่แน่นอนแตกต่างกันระหว่าง Python 2.x และ 3.x ใน 3.x มันทำงานดังนี้:

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

12
ดีและครอบคลุม
kindall

ขอบคุณสำหรับคำตอบ. ฉันจะลองใช้วิธีการนำเข้า shared_stuff แม้ว่าฉันจะไม่สามารถเข้าใจได้ว่าตัวแปรที่กำหนดไว้ในโมดูลหลักนั้นไม่ได้เป็นแบบสากล - ไม่มีวิธีใดที่จะทำให้ทุกคนสามารถเข้าถึงชื่อได้อย่างแท้จริงจากทุกที่ในโปรแกรม เหรอ?
Nubarke

1
การมีบางสิ่งที่ "เข้าถึงได้สำหรับทุกคนจากโปรแกรมใด ๆ ก็ตาม" นั้นขัดแย้งกับzen of pythonโดยเฉพาะ "Explicit ดีกว่าโดยนัย" Python มีการออกแบบ oo ที่คิดมาเป็นอย่างดีและถ้าคุณใช้มันได้ดีคุณอาจจัดการอาชีพ Python ที่เหลือได้โดยไม่จำเป็นต้องใช้คีย์เวิร์ดทั่วโลกอีกเลย
Bi Rico

1
UPDATE: ขอบคุณ! วิธีการ shared_stuff ทำงานได้ดีด้วยวิธีนี้ฉันคิดว่าฉันสามารถแก้ไขปัญหาอื่น ๆ ได้
Nubarke

1
@DanielArmengod: ฉันได้เพิ่มคำตอบในตัวในกรณีที่คุณต้องการจริงๆ (และหากคุณต้องการรายละเอียดเพิ่มเติมให้ค้นหา SO มีคำถามอย่างน้อยสองข้อเกี่ยวกับวิธีการเพิ่มสิ่งต่างๆในบิวด์อินอย่างถูกต้อง) แต่อย่างที่ Bi Rico กล่าวไว้คุณแทบจะไม่ต้องการหรือต้องการจริงๆ
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

ฟังก์ชั่นใช้ Globals ของโมดูลมันเป็นที่กำหนดไว้ใน. แทนการตั้งค่าตัวอย่างเช่นคุณควรจะตั้งค่าa = 3 module1.a = 3ดังนั้นถ้าคุณต้องการcurใช้ได้เป็นในระดับโลกชุดutilities_moduleutilities_module.cur

ทางออกที่ดีกว่า: อย่าใช้ globals ส่งผ่านตัวแปรที่คุณต้องการไปยังฟังก์ชันที่ต้องการหรือสร้างคลาสเพื่อรวมข้อมูลทั้งหมดเข้าด้วยกันและส่งผ่านไปเมื่อเริ่มต้นอินสแตนซ์


แทนที่จะเขียน 'import module1' หากผู้ใช้เขียน 'from module1 import f' ดังนั้น f จะมาอยู่ในเนมสเปซส่วนกลางของ main.py ตอนนี้ใน main.py ถ้าเราใช้ f () ดังนั้นเนื่องจาก a = 3 และ f (นิยามฟังก์ชัน) ทั้งคู่อยู่ใน globalnamespace ของ main นี่คือวิธีแก้ปัญหาหรือไม่? หากฉันผิดโปรดช่วยแนะนำฉันไปยังบทความในเรื่องนี้ได้ไหม
ตัวแปร

ฉันใช้วิธีการด้านบนและส่งผ่าน globals เป็นอาร์กิวเมนต์เมื่อสร้างอินสแตนซ์คลาสที่ใช้ globals ไม่เป็นไร แต่ในบางครั้งต่อมาเราได้เปิดใช้งานการตรวจสอบรหัส Sonarqube ของรหัสและสิ่งนี้บ่นว่าฟังก์ชันมีข้อโต้แย้งมากเกินไป เราจึงต้องหาวิธีอื่น ตอนนี้เราใช้ตัวแปรสภาพแวดล้อมที่อ่านโดยแต่ละโมดูลที่ต้องการ มันไม่ได้เป็นไปตาม OOP จริงๆ แต่นั่นแหล่ะ สิ่งนี้ใช้ได้เฉพาะเมื่อ globals ไม่เปลี่ยนแปลงในระหว่างการเรียกใช้โค้ด
rimetnac

5

โพสต์นี้เป็นเพียงการสังเกตพฤติกรรม Python ที่ฉันพบ บางทีคำแนะนำที่คุณอ่านด้านบนอาจไม่ได้ผลสำหรับคุณหากคุณทำสิ่งเดียวกับที่ฉันทำด้านล่าง

กล่าวคือฉันมีโมดูลที่มีตัวแปรส่วนกลาง / ที่ใช้ร่วมกัน (ตามที่แนะนำข้างต้น):

#sharedstuff.py

globaltimes_randomnode=[]
globalist_randomnode=[]

จากนั้นฉันมีโมดูลหลักที่นำเข้าสิ่งที่แชร์ด้วย:

import sharedstuff as shared

และโมดูลอื่น ๆ ที่เติมอาร์เรย์เหล่านี้จริง สิ่งเหล่านี้เรียกโดยโมดูลหลัก เมื่อออกจากโมดูลอื่น ๆ เหล่านี้ฉันสามารถเห็นได้อย่างชัดเจนว่ามีการเติมอาร์เรย์ แต่เมื่ออ่านย้อนกลับไปในโมดูลหลักกลับว่างเปล่า สิ่งนี้ค่อนข้างแปลกสำหรับฉัน (ฉันยังใหม่กับ Python) อย่างไรก็ตามเมื่อฉันเปลี่ยนวิธีนำเข้า sharedstuff.py ในโมดูลหลักเป็น:

from globals import *

มันใช้งานได้ (อาร์เรย์ถูกเติม)

แค่บอกว่า


2

วิธีแก้ปัญหาที่ง่ายที่สุดสำหรับปัญหานี้คือการเพิ่มฟังก์ชันอื่นภายในโมดูลที่จะเก็บเคอร์เซอร์ไว้ในตัวแปร global ไปยังโมดูล จากนั้นฟังก์ชันอื่น ๆ ทั้งหมดก็สามารถใช้งานได้เช่นกัน

โมดูล 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

เนื่องจาก globals เป็นโมดูลเฉพาะคุณสามารถเพิ่มฟังก์ชันต่อไปนี้ให้กับโมดูลที่นำเข้าทั้งหมดจากนั้นใช้เพื่อ:

  • เพิ่มตัวแปรเอกพจน์ (ในรูปแบบพจนานุกรม) เป็นลูกโลกสำหรับตัวแปรเหล่านั้น
  • โอนย้ายโมดูลหลักของคุณไปที่มัน

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

จากนั้นสิ่งที่คุณต้องส่งต่อไปยังลูกโลกปัจจุบันคือ:

โมดูลนำเข้า

module.addglobals (ลูกโลก ())


1

เนื่องจากฉันไม่เห็นมันในคำตอบข้างต้นฉันคิดว่าฉันจะเพิ่มวิธีแก้ปัญหาง่ายๆของฉันซึ่งเป็นเพียงการเพิ่มglobal_dictอาร์กิวเมนต์ให้กับฟังก์ชันที่ต้องใช้ globals ของโมดูลการโทรจากนั้นส่งคำสั่งไปยังฟังก์ชันเมื่อเรียก เช่น:

# 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 เพื่อตั้งค่าตัวแปรจากผู้เรียกเพื่อใช้ในวิธีการของโมดูล

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.