Python: mengimpor sub ‑ paket atau sub ‑ modul

91

Setelah menggunakan paket datar, saya tidak mengharapkan masalah yang saya temui dengan paket bersarang. Disini adalah…

Tata letak direktori

dir
 |
 +-- test.py
 |
 +-- package
      |
      +-- __init__.py
      |
      +-- subpackage
           |
           +-- __init__.py
           |
           +-- module.py

Isi init .py

Keduanya package/__init__.pydan package/subpackage/__init__.pykosong.

Isi dari module.py

# file `package/subpackage/module.py`
attribute1 = "value 1"
attribute2 = "value 2"
attribute3 = "value 3"
# and as many more as you want...

Isi dari test.py(3 versi)

Versi 1

# file test.py
from package.subpackage.module import *
print attribute1 # OK

Itu cara yang buruk dan tidak aman untuk mengimpor barang (impor semua dalam jumlah besar), tetapi berhasil.

Versi 2

# file test.py
import package.subpackage.module
from package.subpackage import module # Alternative
from module import attribute1

Cara yang lebih aman untuk mengimpor, item demi item, tetapi gagal, Python tidak menginginkan ini: gagal dengan pesan: "No module bernama module". Namun…

# file test.py
import package.subpackage.module
from package.subpackage import module # Alternative
print module # Surprise here

… Berkata <module 'package.subpackage.module' from '...'>. Jadi itu modul, tapi itu bukan modul / -P 8-O ... uh

Versi 3

# file test.py v3
from package.subpackage.module import attribute1
print attribute1 # OK

Yang ini berhasil. Jadi Anda terpaksa menggunakan awalan yang berlebihan sepanjang waktu atau menggunakan cara yang tidak aman seperti pada versi # 1 dan tidak diizinkan oleh Python untuk menggunakan cara praktis yang aman? Cara yang lebih baik, mana yang aman dan menghindari awalan panjang yang tidak perlu adalah satu-satunya yang ditolak Python? Apakah ini karena cinta import *atau karena menyukai prefiks yang terlalu panjang (yang tidak membantu untuk menegakkan praktik ini)?

Maaf untuk kata-kata sulitnya, tapi itu dua hari saya mencoba untuk mengatasi perilaku bodoh ini. Kecuali jika saya benar-benar salah, ini akan membuat saya merasa ada sesuatu yang benar-benar rusak dalam model paket dan sub-paket Python.

Catatan

  • Saya tidak ingin mengandalkan sys.path, untuk menghindari efek samping global, atau *.pthfile, yang hanya merupakan cara lain untuk bermain sys.pathdengan efek global yang sama. Agar solusinya bersih, harus lokal saja. Entah Python dapat menangani sub-paket, baik itu tidak, tetapi tidak perlu bermain dengan konfigurasi global untuk dapat menangani barang-barang lokal.
  • Saya juga mencoba menggunakan impor package/subpackage/__init__.py, tetapi tidak menyelesaikan apa-apa, melakukan hal yang sama, dan komplain subpackagebukan modul yang dikenal, sementara print subpackagemengatakan ini adalah modul (perilaku aneh, sekali lagi).

Mungkin saya sepenuhnya salah tangguh (opsi yang saya lebih suka), tetapi ini membuat saya merasa sangat kecewa dengan Python.

Adakah cara lain yang diketahui selain dari ketiganya yang saya coba? Sesuatu yang tidak saya ketahui?

(mendesah)

-----% <----- edit ----->% -----

Kesimpulan sejauh ini (setelah komentar orang)

Tidak ada sub-paket nyata dalam Python, karena semua referensi paket hanya masuk ke kamus global, yang berarti tidak ada kamus lokal, yang berarti tidak ada cara untuk mengelola referensi paket lokal.

Anda harus menggunakan prefiks penuh atau prefiks pendek atau alias. Seperti dalam:

Versi awalan lengkap

from package.subpackage.module import attribute1
# An repeat it again an again
# But after that, you can simply:
use_of (attribute1)

Versi awalan pendek (tapi awalan berulang)

from package.subpackage import module
# Short but then you have to do:
use_of (module.attribute1)
# and repeat the prefix at every use place

Atau, variasi di atas.

from package.subpackage import module as m
use_of (m.attribute1)
# `m` is a shorter prefix, but you could as well
# define a more meaningful name after the context

Versi terfaktor

Jika Anda tidak keberatan mengimpor beberapa entitas sekaligus dalam satu batch, Anda dapat:

from package.subpackage.module import attribute1, attribute2
# and etc.

Tidak dalam rasa favorit pertama saya (saya lebih suka memiliki satu pernyataan impor per entitas yang diimpor), tetapi mungkin yang saya sukai secara pribadi.

Pembaruan (2012-09-14):

Akhirnya tampaknya OK dalam praktiknya, kecuali dengan komentar tentang tata letak. Alih-alih di atas, saya menggunakan:

from package.subpackage.module import (

    attribute1, 
    attribute2,
    attribute3,
    ...)  # and etc.
Hibou57
sumber
Bagaimana kabarnya saat Anda menulis "from. Import module" ke "/package/subpackage/__init__.py"?
Markus Unterwaditzer
"Versi faktorisasi" Anda tampaknya tepat untuk apa yang ingin Anda lakukan. Jika Anda melakukan baris impor terpisah untuk atribut1 dan atribut2 (sesuai keinginan Anda), Anda hanya sengaja memberi lebih banyak pekerjaan. Tidak ada alasan untuk melakukan itu.
BrenBarn
Maaf, tetapi saya tidak mendapatkan apa yang Anda inginkan. Bisakah Anda mengubah pertanyaan Anda dengan cara yang lebih jelas? Apa yang sebenarnya ingin Anda lakukan? Maksud saya, apa yang ingin Anda tulis yang tidak berhasil dan bagaimana Anda mengharapkannya? Dengan apa yang saya baca saya pikir Anda apa semantik dari impor menjadi seperti Java atau mungkin C termasuk. Hal terakhir: Anda dapat membuat modul "star-import" aman dengan menambahkan __all__variabel yang berisi daftar nama yang harus diekspor saat diimpor. edit: Oke, membaca jawaban BrenBarn saya mengerti apa yang Anda maksud.
Bakuriu

Jawaban:

69

Anda tampaknya salah paham tentang cara importmenelusuri modul. Saat Anda menggunakan pernyataan import, ia selalu mencari jalur modul aktual (dan / atau sys.modules); itu tidak menggunakan objek modul di namespace lokal yang ada karena impor sebelumnya. Saat kamu melakukan:

import package.subpackage.module
from package.subpackage import module
from module import attribute1

Baris kedua mencari paket yang dipanggil package.subpackagedan mengimpor moduledari paket itu. Baris ini tidak berpengaruh pada baris ketiga. Baris ketiga hanya mencari modul yang dipanggil moduledan tidak menemukannya. Itu tidak "menggunakan kembali" objek yang disebut moduleyang Anda dapatkan dari baris di atas.

Dengan kata lain from someModule import ...tidak berarti "dari modul bernama someModule yang saya impor sebelumnya ..." itu berarti "dari modul bernama someModule yang Anda temukan di sys.path ...". Tidak ada cara untuk "secara bertahap" membangun jalur modul dengan mengimpor paket yang mengarah ke sana. Anda selalu harus merujuk ke seluruh nama modul saat mengimpor.

Tidak jelas apa yang ingin Anda capai. Jika Anda hanya ingin mengimpor atribut objek1 tertentu, lakukan from package.subpackage.module import attribute1dan selesaikan saja. Anda tidak perlu khawatir tentang lama package.subpackage.modulesetelah Anda mengimpor nama yang Anda inginkan darinya.

Jika Anda tidak ingin memiliki akses ke modul untuk mengakses nama lain nanti, maka Anda dapat melakukan from package.subpackage import moduledan, karena Anda telah melihat Anda kemudian dapat melakukan module.attribute1dan sebagainya sebanyak yang Anda suka.

Jika Anda menginginkan keduanya --- yaitu, jika Anda ingin attribute1dapat diakses secara langsung dan ingin moduledapat diakses, cukup lakukan kedua hal di atas:

from package.subpackage import module
from package.subpackage.module import attribute1
attribute1 # works
module.someOtherAttribute # also works

Jika Anda tidak suka mengetik package.subpackagedua kali, Anda dapat membuat referensi lokal ke atribut1 secara manual:

from package.subpackage import module
attribute1 = module.attribute1
attribute1 # works
module.someOtherAttribute #also works
BrenBarn
sumber
Komentar Anda mengarah ke arah yang sama dengan komentar dari Ignacio Vazquez-Abrams (saya telah mengomentari pesannya). Anda menambahkan di akhir, tentang penggunaan module.attribute1adalah sesuatu yang saya pikirkan, tapi saya pikir akan ada cara untuk menghindari perlunya prefiks di mana-mana. Jadi saya harus menggunakan awalan di mana-mana, atau membuat alias lokal, mengulangi nama tersebut. Bukan gaya yang saya harapkan, tetapi jika tidak ada cara (lagipula, saya sudah terbiasa dengan Ada, yang membutuhkan sesuatu yang mirip dengan deklarasi penggantian nama).
Hibou57
@ Hibou57: Masih belum jelas bagi saya apa yang Anda coba capai dalam "Versi 2" Anda. Apa yang ingin Anda lakukan yang tidak mungkin? Anda tidak ingin pernah mengetik ulang bagian mana pun dari nama paket / modul / atribut, tetapi masih mengimpor modul dan atributnya?
BrenBarn
Saya ingin memiliki referensi paket lokal, seperti cara Anda memiliki referensi objek lokal. Sepertinya akhirnya ada referensi modul yang benar-benar lokal, tetapi Anda tidak dapat mengimpor dari ini. Ini campuran lokal dan global dengan rasa yang lucu (beberapa barang bisa lokal, beberapa lainnya harus global, saya tidak suka, tapi saya baik-baik saja selama saya lebih memahami cara kerjanya). Terima kasih atas pesan Anda.
Hibou57
1
Saya tidak yakin Anda masih mengerti cara kerjanya. Atau yang Anda lakukan di tahun 2012 dalam hal apa pun.
Hejazzman
1
Setiap kali saya kembali ke Python setelah PHK 6 bulan, saya berakhir di sini. Kalau saja saya bisa memberi suara positif setiap kali saya mengunjungi halaman ini! Saya akan membuat poster raksasa dengan kalimat ini: "Tidak ada cara untuk" secara bertahap "membangun jalur modul dengan mengimpor paket yang mengarah ke sana."
PatrickT
10

Alasan # 2 gagal adalah karena sys.modules['module']tidak ada (rutin impor memiliki cakupannya sendiri, dan tidak dapat melihat modulenama lokal), dan tidak ada modulemodul atau paket di disk. Perhatikan bahwa Anda dapat memisahkan beberapa nama yang diimpor dengan koma.

from package.subpackage.module import attribute1, attribute2, attribute3

Juga:

from package.subpackage import module
print module.attribute1
Ignacio Vazquez-Abrams
sumber
Referensi Anda sys.modules['name']yang saya tidak tahu sampai sekarang, membuat saya berpikir itulah yang saya takuti (dan BrenBarn menegaskan): tidak ada yang seperti sub-paket nyata dalam Python. sys.modules, seperti yang disarankan namanya, bersifat global, dan jika semua referensi ke modul mengandalkan ini, maka tidak ada yang seperti referensi lokal ke modul (mungkin datang dengan Python 3.x?).
Hibou57
Penggunaan "referensi" oleh Anda tidak jelas; yang pertama importdi # 2 menghasilkan referensi lokal untuk package.subpackage.moduleterikat module.
Ignacio Vazquez-Abrams
Ya, tapi itu adalah "modul" yang tidak dapat saya impor ;-)
Hibou57
0

Jika semua yang Anda coba lakukan adalah mendapatkan atribut1 di namespace global Anda, versi 3 tampaknya baik-baik saja. Mengapa prefiks itu berlebihan?

Di versi 2, bukan

from module import attribute1

Anda dapat melakukan

attribute1 = module.attribute1
Thomas Vander Stichele
sumber
attribute1 = module.attribute1hanya mengulang nama tanpa nilai tambah. Saya tahu ini berhasil, tetapi saya tidak suka gaya ini (yang tidak berarti saya tidak suka balasan Anda).
Hibou57
2
Saya rasa saya, seperti semua orang yang berkomentar di sini, tidak mengerti apa yang Anda ingin lakukan. Dalam semua contoh yang Anda berikan, sepertinya Anda ingin mendapatkan simbol dari sub-paket di namespace Anda. Contoh Anda yang tidak berfungsi (contoh 2) ingin melakukannya dengan mengimpor submodul dari sebuah paket, lalu mengimpor simbol dari submodul tersebut. Saya tidak tahu mengapa Anda ingin melakukannya dalam dua langkah, bukan satu. Mungkin menjelaskan lebih lanjut apa solusi ideal Anda dan mengapa.
Thomas Vander Stichele