Bagaimana cara mengubah variabel modul dari modul lain?

107

Misalkan saya memiliki sebuah paket bernama bar, dan itu berisi bar.py:

a = None

def foobar():
    print a

dan __init__.py:

from bar import a, foobar

Kemudian saya menjalankan skrip ini:

import bar

print bar.a
bar.a = 1
print bar.a
bar.foobar()

Inilah yang saya harapkan:

None
1
1

Inilah yang saya dapatkan:

None
1
None

Adakah yang bisa menjelaskan kesalahpahaman saya?

shino
sumber

Jawaban:

103

Anda sedang menggunakan from bar import a. amenjadi simbol dalam lingkup global modul pengimporan (atau ruang lingkup apa pun tempat pernyataan impor terjadi).

Saat Anda menetapkan nilai baru a, Anda hanya mengubah apoin nilai mana juga, bukan nilai sebenarnya. Mencoba untuk mengimpor bar.pylangsung dengan import bardi __init__.pydan melakukan percobaan di sana dengan pengaturan bar.a = 1. Dengan cara ini, Anda benar-benar akan memodifikasi bar.__dict__['a']yang merupakan nilai 'nyata' adalam konteks ini.

Ini sedikit berbelit-belit dengan tiga lapisan tetapi bar.a = 1mengubah nilai adalam modul yang disebut baryang sebenarnya berasal __init__.py. Itu tidak mengubah nilai ayang foobardilihat karena foobartinggal di file yang sebenarnya bar.py. Anda dapat mengaturnya bar.bar.ajika ingin mengubahnya.

Ini adalah salah satu bahaya menggunakan from foo import barbentuk importpernyataan: itu terbagi barmenjadi dua simbol, satu terlihat secara global dari dalam fooyang dimulai menunjuk ke nilai asli dan simbol yang berbeda terlihat dalam ruang lingkup di manaimport pernyataan itu dijalankan. Mengubah tempat titik simbol tidak mengubah nilai yang ditunjukkannya juga.

Hal semacam ini sangat mematikan ketika mencoba reloadmodul dari interpreter interaktif.

aaronasterling
sumber
Terima kasih! Jawaban Anda mungkin baru saja menyelamatkan saya beberapa jam untuk mencari pelakunya.
jnns
Sepertinya bar.bar.apendekatan ini tidak akan banyak membantu dalam kebanyakan kasus penggunaan. Mungkin ide yang lemah untuk menggabungkan kompilasi atau pemuatan modul dan tugas waktu proses karena Anda akan membingungkan diri sendiri (atau orang lain yang menggunakan kode Anda). Terutama dalam bahasa yang berperilaku agak tidak konsisten tentangnya, seperti yang bisa dibilang python.
matanster
26

Salah satu sumber kesulitan dengan pertanyaan ini adalah bahwa Anda memiliki sebuah program bernama bar/bar.py: import barimpor baik bar/__init__.pyatau bar/bar.py, tergantung di mana hal itu dilakukan, yang membuatnya sedikit rumit untuk melacak yang amerupakan bar.a.

Berikut cara kerjanya:

Kunci untuk memahami apa yang terjadi adalah dengan menyadari bahwa dalam __init__.py,

from bar import a

pada dasarnya melakukan sesuatu seperti

a = bar.a
# … where bar = bar/bar.py (as if bar were imported locally from __init__.py)

dan mendefinisikan variabel baru ( bar/__init__.py:a, jika Anda mau). Jadi, nama from bar import ain __init__.pymengikat Anda bar/__init__.py:ake bar.py:aobjek asli ( None). Inilah mengapa Anda dapat melakukannya from bar import a as a2di __init__.py: dalam kasus ini, jelas bahwa Anda memiliki keduanya bar/bar.py:adan nama variabel yang berbedabar/__init__.py:a2 (dalam kasus Anda, nama kedua variabel kebetulan saja keduanya a, tetapi mereka masih hidup di ruang nama yang berbeda: di __init__.py, mereka bar.adan a).

Sekarang, saat Anda melakukannya

import bar

print bar.a

Anda mengakses variabel bar/__init__.py:a(karena import barmengimpor Anda bar/__init__.py). Ini adalah variabel yang Anda ubah (menjadi 1). Anda tidak menyentuh isi variabel bar/bar.py:a. Jadi saat Anda selanjutnya melakukannya

bar.foobar()

Anda memanggil bar/bar.py:foobar(), yang mengakses variabel adari bar/bar.py, yang masih None(ketika foobar()ditentukan, itu mengikat nama variabel sekali dan untuk semua, jadi ain bar.pyadalah bar.py:a, bukan avariabel lain yang ditentukan dalam modul lain — karena mungkin ada banyak avariabel di semua modul yang diimpor ). Karenanya Nonekeluaran terakhir .

Kesimpulan: yang terbaik adalah menghindari ambiguitas apa pun import bar, dengan tidak memiliki bar/bar.pymodul apa pun (karena bar.__init__.pymembuat direktori sudah bar/menjadi paket, yang juga dapat Anda impor import bar).

Eric O Lebigot
sumber
12

Dengan kata lain: Ternyata kesalahpahaman ini sangat mudah dilakukan. Ini secara diam-diam didefinisikan dalam referensi bahasa Python: penggunaan objek alih-alih simbol . Saya menyarankan agar referensi bahasa Python membuat ini lebih jelas dan tidak terlalu jarang ..

The frombentuk tidak mengikat nama modul: ia pergi melalui daftar pengenal, terlihat masing-masing dari mereka dalam modul ditemukan pada langkah (1), dan mengikat nama dalam namespace lokal ke objek sehingga ditemukan.

NAMUN:

Saat Anda mengimpor, Anda mengimpor nilai saat ini dari simbol yang diimpor dan menambahkannya ke namespace Anda seperti yang ditentukan. Anda tidak mengimpor referensi, Anda mengimpor nilai secara efektif.

Jadi, untuk mendapatkan nilai yang diperbarui i, Anda harus mengimpor variabel yang memiliki referensi ke simbol itu.

Dengan kata lain, mengimpor TIDAK seperti importdi JAVA, externaldeklarasi di C / C ++ atau bahkan useklausa di PERL.

Sebaliknya, pernyataan berikut dengan Python:

from some_other_module import a as x

lebih seperti kode berikut di K&R C:

extern int a; /* import from the EXTERN file */

int x = a;

(peringatan: dalam kasus Python, "a" dan "x" pada dasarnya adalah referensi ke nilai sebenarnya: Anda tidak menyalin INT, Anda menyalin alamat referensi)

Mark Gerolimatos
sumber
Sebenarnya, menurut saya cara Python importjauh lebih bersih daripada Java, karena namespace / cakupan harus selalu dipisahkan dengan benar dan tidak pernah saling mengganggu dengan cara yang tidak terduga. Seperti di sini: Mengubah pengikatan objek menjadi nama di namespace (baca: memasukkan sesuatu ke properti global modul) tidak pernah memengaruhi namespace lain (baca: referensi yang diimpor) dengan Python. Tapi melakukannya di Java, dll. Dengan Python Anda hanya perlu memahami apa yang diimpor, sedangkan di Java, Anda juga harus memahami modul lain jika nanti akan mengubah nilai yang diimpor ini.
Tino
2
Saya harus sangat tidak setuju. Mengimpor / memasukkan / menggunakan memiliki sejarah panjang dalam menggunakan formulir referensi dan bukan bentuk nilai di hampir setiap bahasa lainnya.
Mark Gerolimatos
4
Sialan, saya tekan kembali ... ada harapan impor dengan referensi (sesuai @OP). Faktanya, seorang programmer Python baru, tidak peduli seberapa berpengalaman, harus diberitahu tentang hal ini dengan cara "melihat keluar". Itu seharusnya tidak pernah terjadi: berdasarkan penggunaan umum, Python mengambil jalan yang salah. Buat "nilai impor" jika perlu, tetapi jangan gabungkan simbol dengan nilai pada saat mengimpor.
Mark Gerolimatos