Bagaimana cara membuat variabel lintas modul?

122

The __debug__variabel berguna sebagian karena mempengaruhi setiap modul. Jika saya ingin membuat variabel lain yang bekerja dengan cara yang sama, bagaimana saya melakukannya?

Variabel (mari kita menjadi orisinal dan menyebutnya 'foo') tidak harus benar-benar global, dalam artian jika saya mengubah foo dalam satu modul, itu diperbarui di modul lain. Saya akan baik-baik saja jika saya dapat mengatur foo sebelum mengimpor modul lain dan kemudian mereka akan melihat nilai yang sama untuknya.

Dan Homerick
sumber

Jawaban:

114

Saya tidak mendukung solusi ini dengan cara, bentuk, atau bentuk apa pun. Tetapi jika Anda menambahkan variabel ke __builtin__modul, itu akan dapat diakses seolah-olah global dari modul lain yang menyertakan __builtin__- yang semuanya, secara default.

a.py berisi

print foo

b.py berisi

import __builtin__
__builtin__.foo = 1
import a

Hasilnya adalah "1" dicetak.

Edit: The __builtin__Modul tersedia sebagai simbol lokal __builtins__- itulah alasan untuk perbedaan antara dua jawaban ini. Perhatikan juga bahwa __builtin__telah diubah namanya menjadi builtinsdi python3.

Curt Hagenlocher
sumber
2
Ada alasan, bahwa Anda tidak menyukai situasi ini?
Antusias Perangkat Lunak
31
Untuk satu hal, itu merusak harapan orang ketika mereka membaca kode. "Apa simbol 'foo' yang digunakan di sini? Mengapa saya tidak bisa melihat di mana itu didefinisikan?"
Curt Hagenlocher
9
Itu juga rentan untuk mendatangkan malapetaka jika versi Python masa depan mulai menggunakan nama yang Anda pilih sebagai bawaan yang sebenarnya.
Intuisi
4
Ini adalah solusi yang bagus untuk hal-hal seperti berbagi koneksi db dengan modul yang diimpor. Sebagai pemeriksaan kewarasan, saya memastikan bahwa modul yang diimpor menegaskan hasattr(__builtin__, "foo").
Mike Ellis
4
Untuk siapa pun yang membaca jawaban ini: JANGAN! LAKUKAN! INI! Sungguh, jangan.
bruno desthuilliers
161

Jika Anda memerlukan variabel lintas-modul global mungkin hanya variabel tingkat modul global yang sederhana sudah cukup.

a.py:

var = 1

b.py:

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

c.py:

import a
a.var = 2

Uji:

$ python b.py
# -> 1 2

Contoh dunia nyata: global_settings.py Django (meskipun dalam pengaturan aplikasi Django digunakan dengan mengimpor objek django.conf.settings ).

jfs
sumber
3
Lebih baik karena menghindari kemungkinan konflik namespace
bgw
Bagaimana jika modul yang Anda impor, dalam hal ini a.py, berisi main()? Apakah itu penting?
sedeh
4
@sedeh: tidak. Jika a.py juga dijalankan sebagai skrip, gunakan if __name__=="__main__"pelindung di dalamnya untuk menghindari menjalankan kode tak terduga saat impor.
jfs
6
Di dunia nyata, Anda harus sedikit berhati-hati dengan solusi ini. Jika seorang programmer mengambil variabel 'global' Anda menggunakan 'from a import var', (coba variasi ini di c.py) mereka mendapatkan salinan variabel tersebut pada saat mengimpor.
Paul Whipp
1
@PaulWhipp: salah (petunjuk: gunakan id()untuk memeriksa identitas)
jfs
25

Saya percaya bahwa ada banyak keadaan di mana itu masuk akal dan menyederhanakan pemrograman untuk memiliki beberapa global yang dikenal di beberapa modul (berpasangan erat). Dalam semangat ini, saya ingin menguraikan sedikit tentang gagasan memiliki modul global yang diimpor oleh modul-modul yang perlu direferensikan.

Jika hanya ada satu modul, saya beri nama "g". Di dalamnya, saya menetapkan nilai default untuk setiap variabel yang ingin saya perlakukan sebagai global. Di setiap modul yang menggunakan salah satunya, saya tidak menggunakan "from g import var", karena ini hanya menghasilkan variabel lokal yang diinisialisasi dari g hanya pada saat impor. Saya membuat sebagian besar referensi dalam bentuk g.var, dan "g". berfungsi sebagai pengingat bahwa saya berurusan dengan variabel yang berpotensi dapat diakses oleh modul lain.

Jika nilai variabel global seperti itu akan sering digunakan dalam beberapa fungsi dalam modul, maka fungsi itu dapat membuat salinan lokal: var = g.var. Namun, penting untuk disadari bahwa penugasan ke var bersifat lokal, dan global g.var tidak dapat diperbarui tanpa merujuk g.var secara eksplisit dalam penugasan.

Perhatikan bahwa Anda juga dapat memiliki beberapa modul global yang dibagikan oleh subset berbeda dari modul Anda untuk menjaga agar hal-hal sedikit lebih terkontrol dengan ketat. Alasan saya menggunakan nama pendek untuk modul global saya adalah untuk menghindari kode yang terlalu berantakan dengan kemunculannya. Dengan hanya sedikit pengalaman, mereka menjadi cukup mnemonik dengan hanya 1 atau 2 karakter.

Masih dimungkinkan untuk membuat penugasan ke, katakanlah, gx ketika x belum didefinisikan dalam g, dan modul lain kemudian dapat mengakses gx Namun, meskipun penerjemah mengizinkannya, pendekatan ini tidak begitu transparan, dan saya menghindari Itu. Masih ada kemungkinan secara tidak sengaja membuat variabel baru di g karena salah ketik pada nama variabel untuk tugas. Kadang-kadang pemeriksaan dir (g) berguna untuk menemukan nama-nama kejutan yang mungkin muncul karena kecelakaan tersebut.

David Vanderschel
sumber
7
Pengamatan yang menarik ini memecahkan masalah saya: 'Saya tidak menggunakan "dari g import var", karena ini hanya menghasilkan variabel lokal yang diinisialisasi dari g hanya pada saat impor.' Tampaknya masuk akal untuk mengasumsikan bahwa "from..import" sama dengan "import" tetapi ini tidak benar.
Curtis Yallop
24

Definisikan modul (sebut saja "globalbaz") dan tetapkan variabel di dalamnya. Semua modul yang menggunakan "pseudoglobal" ini harus mengimpor modul "globalbaz", dan merujuknya menggunakan "globalbaz.var_name"

Ini berfungsi terlepas dari tempat perubahan, Anda dapat mengubah variabel sebelum atau setelah impor. Modul yang diimpor akan menggunakan nilai terbaru. (Saya menguji ini dalam contoh mainan)

Untuk klarifikasi, globalbaz.py terlihat seperti ini:

var_name = "my_useful_string"
hayalci
sumber
9

Anda dapat meneruskan global dari satu modul ke modul lainnya:

Di Modul A:

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

Di Modul B:

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

Variabel global biasanya merupakan ide yang buruk, tetapi Anda dapat melakukannya dengan menetapkan ke __builtins__:

__builtins__.foo = 'something'
print foo

Selain itu, modul itu sendiri adalah variabel yang dapat Anda akses dari modul apa pun. Jadi jika Anda mendefinisikan modul yang disebut my_globals.py:

# my_globals.py
foo = 'something'

Kemudian Anda juga dapat menggunakannya dari mana saja:

import my_globals
print my_globals.foo

Menggunakan modul daripada memodifikasi __builtins__umumnya merupakan cara yang lebih bersih untuk melakukan global semacam ini.

spiv
sumber
3
__builtins__adalah kekhasan CPython, Anda seharusnya tidak menggunakannya - gunakan lebih baik __builtin__(atau builtinsdi Python3) seperti yang ditunjukkan jawaban yang diterima
Tobias Kienzler
5

Anda sudah dapat melakukan ini dengan variabel tingkat modul. Modul tetap sama tidak peduli dari modul mana mereka diimpor. Jadi, Anda dapat menjadikan variabel sebagai variabel tingkat modul dalam modul apa pun yang masuk akal untuk dimasukkan, dan mengaksesnya atau menetapkannya dari modul lain. Akan lebih baik untuk memanggil fungsi untuk menyetel nilai variabel, atau menjadikannya properti dari beberapa objek tunggal. Dengan cara itu jika Anda akhirnya perlu menjalankan beberapa kode ketika variabel berubah, Anda dapat melakukannya tanpa merusak antarmuka eksternal modul Anda.

Biasanya ini bukan cara yang bagus untuk melakukan sesuatu - menggunakan global jarang - tapi menurut saya ini adalah cara terbersih untuk melakukannya.

intuitif
sumber
3

Saya ingin memposting jawaban bahwa ada kasus di mana variabel tidak akan ditemukan.

Impor siklis dapat merusak perilaku modul.

Sebagai contoh:

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

Pada contoh ini, seharusnya sudah jelas, tetapi dalam basis kode yang besar, ini bisa sangat membingungkan.

Jonathan
sumber
1

Ini terdengar seperti memodifikasi __builtin__ruang nama. Untuk melakukannya:

import __builtin__
__builtin__.foo = 'some-value'

Jangan gunakan __builtins__langsung (perhatikan "s" tambahan) - tampaknya ini bisa berupa kamus atau modul. Terima kasih kepada ΤΖΩΤΖΙΟΥ karena telah menunjukkan hal ini, lebih banyak lagi dapat ditemukan di sini .

Sekarang footersedia untuk digunakan di mana saja.

Saya tidak menyarankan melakukan ini secara umum, tetapi penggunaan ini terserah programmer.

Menetapkannya harus dilakukan seperti di atas, pengaturan foo = 'some-other-value'hanya akan mengaturnya di namespace saat ini.

awatts
sumber
1
Saya ingat (dari comp.lang.python) bahwa menggunakan builtins secara langsung harus dihindari; alih-alih, impor bawaan dan gunakan itu, seperti yang disarankan Curt Hagenlocher.
tzot
1

Saya menggunakan ini untuk beberapa fungsi primitif built-in yang saya rasa benar-benar hilang. Salah satu contohnya adalah fungsi find yang memiliki penggunaan semantik yang sama seperti filter, map, reduce.

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

import __builtin__
__builtin__.find = builtin_find

Setelah ini dijalankan (misalnya, dengan mengimpor di dekat titik masuk Anda) semua modul Anda dapat menggunakan find () seolah-olah, tentu saja, itu sudah terpasang.

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

Catatan: Anda dapat melakukan ini, tentu saja, dengan filter dan garis lain untuk menguji panjang nol, atau dengan pengurangan dalam satu jenis garis yang aneh, tetapi saya selalu merasa itu aneh.

Brian Arsuaga
sumber
1

Saya dapat mencapai variabel lintas modul yang dapat dimodifikasi (atau dapat diubah ) dengan menggunakan kamus:

# 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

Saat meluncurkan test_wait_app_up_fail, durasi waktu tunggu yang sebenarnya adalah 3 detik.

foudfou.dll
sumber
1

Saya bertanya-tanya apakah mungkin untuk menghindari beberapa kerugian menggunakan variabel global (lihat misalnya http://wiki.c2.com/?GlobalVariablesAreBad ) dengan menggunakan namespace kelas daripada namespace global / modul untuk meneruskan nilai variabel . Kode berikut menunjukkan bahwa kedua metode pada dasarnya identik. Ada sedikit keuntungan dalam menggunakan ruang nama kelas seperti yang dijelaskan di bawah ini.

Fragmen kode berikut juga menunjukkan bahwa atribut atau variabel dapat dibuat dan dihapus secara dinamis di ruang nama global / modul dan ruang nama kelas.

wall.py

# Note no definition of global variables

class router:
    """ Empty class """

Saya menyebut modul ini 'dinding' karena digunakan untuk memantulkan variabel. Ini akan bertindak sebagai ruang untuk sementara mendefinisikan variabel global dan atribut kelas-kelas dari 'router' kelas kosong.

source.py

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

Modul ini mengimpor dinding dan mendefinisikan satu fungsi sourcefnyang mendefinisikan pesan dan memancarkannya dengan dua mekanisme berbeda, satu melalui global dan satu melalui fungsi router. Perhatikan bahwa variabel wall.msgdan wall.router.messagedidefinisikan di sini untuk pertama kalinya di ruang nama masing-masing.

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'

Modul ini mendefinisikan fungsi destfnyang menggunakan dua mekanisme berbeda untuk menerima pesan yang dipancarkan oleh sumber. Ini memungkinkan kemungkinan bahwa variabel 'msg' mungkin tidak ada. destfnjuga menghapus variabel setelah ditampilkan.

main.py

import source, dest

source.sourcefn()

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

Modul ini memanggil fungsi yang telah ditentukan sebelumnya secara berurutan. Setelah panggilan pertama ke dest.destfnvariabel wall.msgdan wall.router.msgtidak ada lagi.

Output dari program ini adalah:

global: Halo dunia!
router: Halo dunia!
global: tidak ada
router pesan : tidak ada pesan

Fragmen kode di atas menunjukkan bahwa modul / global dan mekanisme variabel kelas / kelas pada dasarnya identik.

Jika banyak variabel yang akan dibagikan, polusi namespace dapat dikelola baik dengan menggunakan beberapa modul tipe dinding, misalnya wall1, wall2 dll. Atau dengan mendefinisikan beberapa kelas tipe router dalam satu file. Yang terakhir ini sedikit lebih rapi, jadi mungkin mewakili keuntungan marjinal untuk penggunaan mekanisme variabel kelas.

robertofbaycot
sumber