จะสร้างตัวแปรข้ามโมดูลได้อย่างไร?


122

__debug__ตัวแปรเป็นประโยชน์ส่วนหนึ่งเป็นเพราะมันมีผลต่อทุกโมดูล หากฉันต้องการสร้างตัวแปรอื่นที่ทำงานในลักษณะเดียวกันฉันจะทำอย่างไร

ตัวแปร (ขอให้เป็นต้นฉบับและเรียกว่า 'foo') ไม่จำเป็นต้องเป็นทั่วโลกอย่างแท้จริงในแง่ที่ว่าถ้าฉันเปลี่ยน foo ในโมดูลเดียวตัวแปรนั้นจะถูกอัปเดตในโมดูลอื่น ฉันจะดีถ้าฉันสามารถตั้งค่า foo ก่อนที่จะนำเข้าโมดูลอื่น ๆ จากนั้นพวกเขาจะเห็นค่าเดียวกันสำหรับมัน

คำตอบ:


114

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

a.py ประกอบด้วย

print foo

b.py ประกอบด้วย

import __builtin__
__builtin__.foo = 1
import a

ผลลัพธ์คือพิมพ์ "1"

แก้ไข:__builtin__โมดูลสามารถใช้ได้เป็นสัญลักษณ์ท้องถิ่น__builtins__- นั่นคือเหตุผลสำหรับความแตกต่างระหว่างสองคำตอบเหล่านี้ โปรดทราบว่า__builtin__ได้เปลี่ยนชื่อเป็นbuiltinspython3 แล้ว


2
มีเหตุผลอะไรที่คุณไม่ชอบสถานการณ์นี้?
Software กระตือรือร้น

31
ประการหนึ่งมันทำลายความคาดหวังของผู้คนเมื่อพวกเขาอ่านโค้ด "ที่นี่ใช้สัญลักษณ์ 'foo' คืออะไรทำไมฉันไม่เห็นว่ามีการกำหนดไว้ที่ใด"
Curt Hagenlocher

9
นอกจากนี้ยังมีความเสี่ยงที่จะสร้างความหายนะหาก Python เวอร์ชันในอนาคตเริ่มใช้ชื่อที่คุณเลือกเป็นตัวจริง
สังหรณ์ใจ

4
นี่เป็นทางออกที่ดีสำหรับสิ่งต่างๆเช่นการแชร์การเชื่อมต่อฐานข้อมูลกับโมดูลที่นำเข้า hasattr(__builtin__, "foo")ขณะที่การตรวจสอบสติผมให้แน่ใจว่าโมดูลนำเข้าอ้าง
Mike Ellis

4
สำหรับใครที่อ่านคำตอบนี้: DONT! ทำ! นี้ ! จริงๆอย่าเลย
bruno desthuilliers

161

หากคุณต้องการตัวแปรข้ามโมดูลทั่วโลกบางทีเพียงแค่ตัวแปรระดับโมดูลส่วนกลางธรรมดาก็เพียงพอแล้ว

a.py:

var = 1

b.py:

import a
print a.var
import c
print a.var

c.py:

import a
a.var = 2

ทดสอบ:

$ python b.py
# -> 1 2

ตัวอย่างโลกแห่งความจริง: global_settings.py ของ Django (แม้ว่าในการตั้งค่าแอป Django จะใช้โดยการนำเข้าวัตถุ django.conf.settings )


3
ดีกว่าเพราะหลีกเลี่ยงความขัดแย้งของเนมสเปซที่เป็นไปได้
bgw

จะเป็นอย่างไรหากโมดูลที่คุณกำลังนำเข้าในกรณีa.pyนี้ประกอบด้วยmain()อะไรบ้าง? มันสำคัญหรือไม่?
sedeh

4
@sedeh: ไม่. หากมีการเรียกใช้ a.py เป็นสคริปต์ให้ใช้การif __name__=="__main__"ป้องกันเพื่อหลีกเลี่ยงการเรียกใช้รหัสที่ไม่คาดคิดในการนำเข้า
jfs

6
ในโลกแห่งความเป็นจริงคุณต้องระวังวิธีแก้ปัญหานี้สักหน่อย หากโปรแกรมเมอร์หยิบตัวแปร 'global' ของคุณโดยใช้ 'จากการนำเข้า var' (ลองใช้รูปแบบนี้ใน c.py) พวกเขาจะได้รับสำเนาของตัวแปรในเวลาที่นำเข้า
Paul Whipp

1
@PaulWhipp: ผิด (คำใบ้: ใช้id()เพื่อตรวจสอบตัวตน)
jfs

25

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

เมื่อมีโมดูลดังกล่าวเพียงโมดูลเดียวฉันจึงตั้งชื่อว่า "g" ในนั้นฉันกำหนดค่าเริ่มต้นสำหรับทุกตัวแปรที่ฉันตั้งใจจะถือว่าเป็น global ในแต่ละโมดูลที่ใช้โมดูลใด ๆ ฉันไม่ได้ใช้ "from g import var" เนื่องจากจะส่งผลเฉพาะตัวแปรโลคัลที่เริ่มต้นจาก g ในเวลาที่นำเข้าเท่านั้น ฉันทำการอ้างอิงส่วนใหญ่ในรูปแบบ g.var และ "g." ทำหน้าที่เป็นตัวเตือนตลอดเวลาว่าฉันกำลังจัดการกับตัวแปรที่โมดูลอื่น ๆ อาจเข้าถึงได้

หากต้องใช้ค่าของตัวแปรโกลบอลดังกล่าวบ่อยๆในบางฟังก์ชันในโมดูลฟังก์ชันนั้นสามารถสร้างสำเนาภายในเครื่องได้: var = g.var อย่างไรก็ตามสิ่งสำคัญคือต้องตระหนักว่าการมอบหมายงานให้กับ var เป็นแบบโลคัลและ g.var ส่วนกลางไม่สามารถอัปเดตได้หากไม่อ้างถึง g.var อย่างชัดเจนในงานที่มอบหมาย

โปรดทราบว่าคุณยังสามารถมีโมดูล globals หลายโมดูลที่ใช้ร่วมกันโดยชุดย่อยต่างๆของโมดูลของคุณเพื่อให้ควบคุมสิ่งต่างๆได้แน่นขึ้น เหตุผลที่ฉันใช้ชื่อสั้น ๆ สำหรับโมดูล globals ของฉันคือเพื่อหลีกเลี่ยงไม่ให้โค้ดยุ่งเหยิงมากเกินไปกับเหตุการณ์ที่เกิดขึ้น ด้วยประสบการณ์เพียงเล็กน้อยพวกเขาจะจำได้มากพอโดยมีเพียง 1 หรือ 2 ตัวอักษร

ยังคงเป็นไปได้ที่จะทำการมอบหมายให้พูดว่า gx เมื่อ x ไม่ได้กำหนดไว้แล้วใน g และโมดูลอื่นก็สามารถเข้าถึง gx ได้อย่างไรก็ตามแม้ว่าล่ามจะอนุญาต แต่วิธีนี้ก็ไม่โปร่งใสและฉันหลีกเลี่ยง มัน. ยังคงมีความเป็นไปได้ที่จะสร้างตัวแปรใหม่ใน g โดยไม่ได้ตั้งใจอันเป็นผลมาจากการพิมพ์ชื่อตัวแปรสำหรับงานที่ผิด บางครั้งการตรวจ dir (g) ก็มีประโยชน์ในการค้นหาชื่อที่น่าประหลาดใจที่อาจเกิดขึ้นจากอุบัติเหตุดังกล่าว


7
ข้อสังเกตที่น่าสนใจนี้ช่วยแก้ปัญหาของฉัน: "ฉันไม่ได้ใช้" จาก g import var "เนื่องจากจะส่งผลให้ตัวแปรโลคัลซึ่งเริ่มต้นจาก g ในเวลาที่นำเข้าเท่านั้น ' ดูเหมือนจะสมเหตุสมผลที่จะสันนิษฐานว่า "from..import" เหมือนกับ "import" แต่ไม่เป็นความจริง
Curtis Yallop

24

กำหนดโมดูล (เรียกว่า "globalbaz") และกำหนดตัวแปรไว้ภายใน โมดูลทั้งหมดที่ใช้ "pseudoglobal" นี้ควรนำเข้าโมดูล "globalbaz" และอ้างถึงโดยใช้ "globalbaz.var_name"

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

เพื่อความกระจ่าง globalbaz.py มีลักษณะดังนี้:

var_name = "my_useful_string"

9

คุณสามารถส่งลูกโลกของโมดูลหนึ่งไปยังอีกโมดูลหนึ่ง:

ในโมดูล A:

import module_b
my_var=2
module_b.do_something_with_my_globals(globals())
print my_var

ในโมดูล B:

def do_something_with_my_globals(glob): # glob is simply a dict.
    glob["my_var"]=3

7

ตัวแปรส่วนกลางมักเป็นความคิดที่ไม่ดี แต่คุณสามารถทำได้โดยกำหนดให้กับ__builtins__:

__builtins__.foo = 'something'
print foo

นอกจากนี้โมดูลยังเป็นตัวแปรที่คุณสามารถเข้าถึงได้จากโมดูลใด ๆ ดังนั้นหากคุณกำหนดโมดูลที่เรียกว่าmy_globals.py:

# my_globals.py
foo = 'something'

จากนั้นคุณสามารถใช้งานได้จากทุกที่เช่นกัน:

import my_globals
print my_globals.foo

การใช้โมดูลแทนการปรับเปลี่ยน__builtins__โดยทั่วไปเป็นวิธีที่สะอาดกว่าในการทำ globals ในประเภทนี้


3
__builtins__เป็นความผิดปกติ CPython คุณจริงๆไม่ควรใช้มัน - ใช้ดีกว่า__builtin__(หรือbuiltinsใน Python3) เป็นคำตอบที่ได้รับการยอมรับการแสดง
โทเบียส KIENZLER

5

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

โดยปกติแล้วมันไม่ใช่วิธีที่ยอดเยี่ยมในการทำสิ่งต่างๆ - การใช้ globals แทบจะไม่เกิดขึ้นเลย - แต่ฉันคิดว่านี่เป็นวิธีที่สะอาดที่สุดในการทำ


3

ฉันต้องการโพสต์คำตอบว่ามีบางกรณีที่ไม่พบตัวแปร

การนำเข้าแบบวัฏจักรอาจทำลายพฤติกรรมของโมดูล

ตัวอย่างเช่น:

first.py

import second
var = 1

second.py

import first
print(first.var)  # will throw an error because the order of execution happens before var gets declared.

main.py

import first

ในตัวอย่างนี้ควรชัดเจน แต่ในฐานรหัสขนาดใหญ่อาจทำให้สับสนได้


1

ดูเหมือนว่าการปรับเปลี่ยน__builtin__ช่องว่างชื่อ วิธีทำ:

import __builtin__
__builtin__.foo = 'some-value'

อย่าใช้__builtins__โดยตรง (สังเกต "s" พิเศษ) ซึ่งอาจเป็นพจนานุกรมหรือโมดูลก็ได้ ขอขอบคุณที่ΤΖΩΤΖΙΟΥสำหรับการชี้ออกมานี้เพิ่มเติมสามารถพบได้ที่นี่

ตอนนี้fooสามารถใช้ได้ทุกที่

ฉันไม่แนะนำให้ทำโดยทั่วไป แต่การใช้สิ่งนี้ขึ้นอยู่กับโปรแกรมเมอร์

การกำหนดให้ต้องทำตามด้านบนเพียงแค่การตั้งค่าfoo = 'some-other-value'จะตั้งค่าในเนมสเปซปัจจุบันเท่านั้น


1
ฉันจำได้ (จาก comp.lang.python) ว่าควรหลีกเลี่ยงการใช้บิวด์อินโดยตรง แทนการนำเข้าในตัวและการใช้งานที่เป็นห้วน Hagenlocher ปัญหา
tzot

1

ฉันใช้สิ่งนี้สำหรับฟังก์ชันดั้งเดิมในตัวสองตัวที่ฉันรู้สึกว่าขาดหายไปจริงๆ ตัวอย่างหนึ่งคือฟังก์ชันค้นหาที่มีความหมายการใช้งานเช่นเดียวกับตัวกรองแผนที่ลด

def builtin_find(f, x, d=None):
    for i in x:
        if f(i):
            return i
    return d

import __builtin__
__builtin__.find = builtin_find

เมื่อสิ่งนี้ถูกเรียกใช้ (ตัวอย่างเช่นโดยการนำเข้าใกล้จุดเริ่มต้นของคุณ) โมดูลทั้งหมดของคุณสามารถใช้ find () ได้ราวกับว่ามันถูกสร้างขึ้นในตัว

find(lambda i: i < 0, [1, 3, 0, -5, -10])  # Yields -5, the first negative.

หมายเหตุ: แน่นอนคุณสามารถทำได้โดยใช้ตัวกรองและอีกบรรทัดเพื่อทดสอบความยาวเป็นศูนย์หรือลดเส้นแปลก ๆ แต่ฉันมักจะรู้สึกว่ามันแปลก ๆ


1

ฉันสามารถบรรลุตัวแปรข้ามโมดูลที่แก้ไขได้ (หรือเปลี่ยนแปลงได้ ) โดยใช้พจนานุกรม:

# in myapp.__init__
Timeouts = {} # cross-modules global mutable variables for testing purpose
Timeouts['WAIT_APP_UP_IN_SECONDS'] = 60

# in myapp.mod1
from myapp import Timeouts

def wait_app_up(project_name, port):
    # wait for app until Timeouts['WAIT_APP_UP_IN_SECONDS']
    # ...

# in myapp.test.test_mod1
from myapp import Timeouts

def test_wait_app_up_fail(self):
    timeout_bak = Timeouts['WAIT_APP_UP_IN_SECONDS']
    Timeouts['WAIT_APP_UP_IN_SECONDS'] = 3
    with self.assertRaises(hlp.TimeoutException) as cm:
        wait_app_up(PROJECT_NAME, PROJECT_PORT)
    self.assertEqual("Timeout while waiting for App to start", str(cm.exception))
    Timeouts['WAIT_JENKINS_UP_TIMEOUT_IN_SECONDS'] = timeout_bak

เมื่อเปิดตัวtest_wait_app_up_failระยะหมดเวลาจริงคือ 3 วินาที


1

ฉันสงสัยว่าจะเป็นไปได้หรือไม่ที่จะหลีกเลี่ยงข้อเสียบางประการของการใช้ตัวแปรส่วนกลาง (ดูเช่นhttp://wiki.c2.com/?GlobalVariablesAreBad ) โดยใช้เนมสเปซคลาสแทนเนมสเปซส่วนกลาง / โมดูลเพื่อส่งผ่านค่าของตัวแปร . รหัสต่อไปนี้ระบุว่าทั้งสองวิธีนั้นเหมือนกันเป็นหลัก มีข้อได้เปรียบเล็กน้อยในการใช้เนมสเปซคลาสตามที่อธิบายไว้ด้านล่าง

โค้ดแฟรกเมนต์ต่อไปนี้ยังแสดงว่าแอ็ตทริบิวต์หรือตัวแปรอาจถูกสร้างและลบแบบไดนามิกทั้งในเนมสเปซส่วนกลาง / โมดูลและเนมสเปซคลาส

wall.py

# Note no definition of global variables

class router:
    """ Empty class """

ฉันเรียกโมดูลนี้ว่า 'กำแพง' เนื่องจากใช้ในการเด้งตัวแปรออกจาก มันจะทำหน้าที่เป็นช่องว่างสำหรับกำหนดตัวแปรส่วนกลางชั่วคราวและแอตทริบิวต์ระดับกว้างของคลาสว่าง 'เราเตอร์'

source.py

import wall
def sourcefn():
    msg = 'Hello world!'
    wall.msg = msg
    wall.router.msg = msg

โมดูลนี้นำเข้าวอลล์และกำหนดฟังก์ชันเดียวsourcefnซึ่งกำหนดข้อความและปล่อยออกมาโดยกลไกที่แตกต่างกันสองกลไกหนึ่งผ่านทาง globals และอีกหนึ่งฟังก์ชันผ่านทางฟังก์ชันเราเตอร์ โปรดสังเกตว่าตัวแปรwall.msgและwall.router.messageกำหนดไว้ที่นี่เป็นครั้งแรกในเนมสเปซที่เกี่ยวข้อง

dest.py

import wall
def destfn():

    if hasattr(wall, 'msg'):
        print 'global: ' + wall.msg
        del wall.msg
    else:
        print 'global: ' + 'no message'

    if hasattr(wall.router, 'msg'):
        print 'router: ' + wall.router.msg
        del wall.router.msg
    else:
        print 'router: ' + 'no message'

โมดูลนี้กำหนดฟังก์ชันdestfnที่ใช้กลไกที่แตกต่างกันสองแบบเพื่อรับข้อความที่ส่งมาจากแหล่งที่มา ช่วยให้มีความเป็นไปได้ที่ตัวแปร 'msg' อาจไม่มีอยู่จริง destfnยังลบตัวแปรเมื่อแสดงแล้ว

main.py

import source, dest

source.sourcefn()

dest.destfn() # variables deleted after this call
dest.destfn()

โมดูลนี้เรียกฟังก์ชันที่กำหนดไว้ก่อนหน้านี้ตามลำดับ หลังจากการเรียกครั้งแรกไปdest.destfnยังตัวแปรwall.msgและwall.router.msgไม่มีอยู่อีกต่อไป

ผลลัพธ์จากโปรแกรมคือ:

global: สวัสดีชาวโลก - - '
เราเตอร์: สวัสดีชาวโลก - - '
ส่วนกลาง: ไม่มี
เราเตอร์ข้อความ: ไม่มีข้อความ

ส่วนโค้ดข้างต้นแสดงให้เห็นว่ากลไกตัวแปรโมดูล / global และ class / class นั้นเหมือนกัน

หากต้องใช้ตัวแปรร่วมกันจำนวนมากมลพิษเนมสเปซสามารถจัดการได้โดยใช้โมดูลประเภทผนังหลายโมดูลเช่น wall1, wall2 เป็นต้นหรือโดยการกำหนดคลาสประเภทเราเตอร์หลายประเภทในไฟล์เดียว ข้อหลังเป็นระเบียบกว่าเล็กน้อยดังนั้นอาจแสดงถึงข้อได้เปรียบเล็กน้อยสำหรับการใช้กลไกคลาสตัวแปร

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