Bagaimana cara mengimpor modul yang diberi nama sebagai string?


543

Saya sedang menulis aplikasi Python yang mengambil sebagai perintah sebagai argumen, misalnya:

$ python myapp.py command1

Saya ingin aplikasi dapat dikembangkan, yaitu, untuk dapat menambahkan modul baru yang mengimplementasikan perintah baru tanpa harus mengubah sumber aplikasi utama. Pohon itu terlihat seperti:

myapp/
    __init__.py
    commands/
        __init__.py
        command1.py
        command2.py
    foo.py
    bar.py

Jadi saya ingin aplikasi mencari modul perintah yang tersedia saat runtime dan menjalankan yang sesuai.

Python mendefinisikan fungsi __import__ , yang mengambil string untuk nama modul:

__import __ (nama, global = Tidak ada, penduduk setempat = Tidak ada, fromlist = (), level = 0)

Fungsi ini mengimpor nama modul, berpotensi menggunakan global dan lokal yang diberikan untuk menentukan cara menafsirkan nama dalam konteks paket. Thelistlist memberikan nama-nama objek atau submodul yang harus diimpor dari modul yang diberikan oleh nama.

Sumber: https://docs.python.org/3/library/functions.html# import

Jadi saat ini saya punya sesuatu seperti:

command = sys.argv[1]
try:
    command_module = __import__("myapp.commands.%s" % command, fromlist=["myapp.commands"])
except ImportError:
    # Display error message

command_module.run()

Ini berfungsi dengan baik, saya hanya ingin tahu apakah mungkin ada cara yang lebih idiomatis untuk mencapai apa yang kita lakukan dengan kode ini.

Perhatikan bahwa saya secara khusus tidak ingin menggunakan telur atau titik ekstensi. Ini bukan proyek open-source dan saya tidak berharap ada "plugin". Intinya adalah untuk menyederhanakan kode aplikasi utama dan menghapus kebutuhan untuk memodifikasinya setiap kali modul perintah baru ditambahkan.


Apa yang dilakukan dari fromlist = ["myapp.commands"]?
Pieter Müller

@ PieterMüller: di shell python ketik ini: dir(__import__). Daftar nama harus berupa daftar nama untuk ditiru "dari impor nama ...".
mawimawi


Mulai 2019, Anda harus mencari importlib: stackoverflow.com/a/54956419/687896
Brandt

Jangan gunakan __import__ lihat Python Doc menggunakan importlib.import_module
fcm

Jawaban:


321

Dengan Python lebih tua dari 2,7 / 3.1, itu cukup banyak bagaimana Anda melakukannya.

Untuk versi yang lebih baru, lihat importlib.import_moduleuntuk Python 2 dan dan Python 3 .

Anda dapat menggunakannya execjika Anda mau.

Atau menggunakan __import__Anda dapat mengimpor daftar modul dengan melakukan ini:

>>> moduleNames = ['sys', 'os', 're', 'unittest'] 
>>> moduleNames
['sys', 'os', 're', 'unittest']
>>> modules = map(__import__, moduleNames)

Merobek langsung dari Dive Into Python .


7
apa perbedaan untuk eksekutif?
user1767754

1
Bagaimana Anda bisa menggunakan __init__modul itu?
Dorian Dore

7
Satu masalah dengan solusi ini untuk OP adalah bahwa menjebak pengecualian untuk satu atau dua modul "perintah" yang buruk membuat seluruh aplikasi-perintahnya gagal pada satu pengecualian. Secara pribadi saya akan mengulang setiap impor secara individual yang dibungkus dengan percobaan: mods = __ import __ () \ n kecuali ImportError sebagai kesalahan: laporkan (kesalahan) untuk memungkinkan perintah lain terus bekerja sementara yang buruk diperbaiki.
DevPlayer

7
Masalah lain dengan solusi ini, seperti yang ditunjukkan Denis Malinovsky di bawah ini, adalah bahwa python docs sendiri merekomendasikan untuk tidak menggunakannya __import__. The 2.7 docs: "Karena fungsi ini dimaksudkan untuk digunakan oleh interpreter Python dan bukan untuk penggunaan umum, lebih baik menggunakan importlib.import_module () ..." Saat menggunakan python3, impmodul memecahkan proble ini, seperti yang disebutkan oleh monkut di bawah ini.
LiavK

2
@ JohnWu mengapa Anda perlu foreachketika Anda miliki for element indan map():) :)
cowbert

300

Cara yang disarankan untuk Python 2.7 dan 3.1 dan yang lebih baru adalah dengan menggunakan importlibmodul:

importlib.import_module (nama, paket = Tidak Ada)

Impor modul. Argumen nama menentukan modul apa yang akan diimpor dalam istilah absolut atau relatif (misalnya pkg.mod atau ..mod). Jika nama tersebut ditentukan dalam istilah relatif, maka argumen paket harus diatur ke nama paket yang bertindak sebagai jangkar untuk menyelesaikan nama paket (misalnya import_module ('.. mod', 'pkg.subpkg') akan mengimpor pkg.mod).

misalnya

my_module = importlib.import_module('os.path')

2
Direkomendasikan oleh sumber atau otoritas mana?
michuelnik

66
Dokumentasi menyarankan agar tidak menggunakan __import__fungsi yang mendukung modul yang disebutkan di atas.
Denis Malinovsky

8
Ini berfungsi dengan baik untuk impor os.path; bagaimana from os.path import *?
Nam G VU

5
Saya mendapatkan jawabannya di sini yaitu stackoverflow.com/a/44492879/248616 yaitu. callingglobals().update(my_module.__dict)
Nam G VU

2
@NamGVU, ini adalah metode berbahaya karena mencemari global Anda dan dapat menimpa pengidentifikasi dengan nama yang sama. Tautan yang Anda poskan memiliki versi kode ini yang lebih baik dan lebih baik.
Denis Malinovsky

132

Catatan: imp sudah tidak digunakan lagi karena Python 3.4 mendukung importlib

Seperti disebutkan modul imp memberikan Anda fungsi pemuatan:

imp.load_source(name, path)
imp.load_compiled(name, path)

Saya telah menggunakan ini sebelumnya untuk melakukan sesuatu yang serupa.

Dalam kasus saya, saya mendefinisikan kelas tertentu dengan metode yang ditentukan yang diperlukan. Setelah saya memuat modul saya akan memeriksa apakah kelas ada di modul, dan kemudian membuat instance dari kelas itu, sesuatu seperti ini:

import imp
import os

def load_from_file(filepath):
    class_inst = None
    expected_class = 'MyClass'

    mod_name,file_ext = os.path.splitext(os.path.split(filepath)[-1])

    if file_ext.lower() == '.py':
        py_mod = imp.load_source(mod_name, filepath)

    elif file_ext.lower() == '.pyc':
        py_mod = imp.load_compiled(mod_name, filepath)

    if hasattr(py_mod, expected_class):
        class_inst = getattr(py_mod, expected_class)()

    return class_inst

2
Solusi yang bagus dan sederhana. Saya menulis yang serupa: stamat.wordpress.com/dynamic-module-import-in-python Tapi Anda punya beberapa kekurangan: Bagaimana dengan pengecualian? IOError dan ImportError? Mengapa tidak memeriksa versi yang dikompilasi terlebih dahulu dan kemudian untuk versi sumber. Mengharapkan kelas mengurangi penggunaan kembali dalam kasus Anda.
stamat

3
Pada baris di mana Anda membangun MyClass di modul target Anda menambahkan referensi yang berlebihan ke nama kelas. Ini sudah disimpan expected_classsehingga Anda bisa melakukannya class_inst = getattr(py_mod,expected_name)().
Andrew

1
Perhatikan bahwa jika file byte-dikompilasi dengan benar cocok (dengan akhiran .pyc atau .pyo) ada, itu akan digunakan daripada mengurai file sumber yang diberikan . https://docs.python.org/2/library/imp.html#imp.load_source
cdosborn

1
Kepala: solusi ini berfungsi; namun, modul imp akan ditinggalkan demi impor lib, lihat halaman imp: "" "Sudah tidak digunakan sejak versi 3.4: Paket imp sedang menunggu penghentian demi importlib." ""
Ricardo


15

Saat ini Anda harus menggunakan importlib .

Impor file sumber

The docs benar-benar memberikan resep untuk itu, dan itu berjalan seperti:

import sys
import importlib.util

file_path = 'pluginX.py'
module_name = 'pluginX'

spec = importlib.util.spec_from_file_location(module_name, file_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)

# check if it's all there..
def bla(mod):
    print(dir(mod))
bla(module)

Dengan cara ini Anda dapat mengakses anggota (misalnya, fungsi " hello") dari modul Anda pluginX.py- dalam cuplikan ini dipanggil module- di bawah namespace-nya; Misalnya module.hello(),.

Jika Anda ingin mengimpor anggota (misalnya, " hello") Anda dapat memasukkan module/ pluginXdalam daftar modul dalam memori:

sys.modules[module_name] = module

from pluginX import hello
hello()

Impor paket

Mengimpor paket ( misalnya , pluginX/__init__.py) di bawah dir Anda saat ini sebenarnya mudah:

import importlib

pkg = importlib.import_module('pluginX')

# check if it's all there..
def bla(mod):
    print(dir(mod))
bla(pkg)

3
tambahkan sys.modules[module_name] = moduleke akhir kode Anda jika Anda ingin dapat import module_namesesudahnya
Daniel Braun

@DanielBraun melakukannya buat saya salah> ModuleNotFoundError
Nam G VU

10

Jika Anda menginginkannya di lokal Anda:

>>> mod = 'sys'
>>> locals()['my_module'] = __import__(mod)
>>> my_module.version
'2.6.6 (r266:84297, Aug 24 2010, 18:46:32) [MSC v.1500 32 bit (Intel)]'

sama akan bekerja dengan globals()


2
The dokumentasi untuk built-in locals()fungsi eksplisit memperingatkan bahwa "isi kamus ini tidak harus diubah".
martineau

9

Anda bisa menggunakan exec:

exec("import myapp.commands.%s" % command)

Bagaimana cara mendapatkan pegangan ke modul melalui exec, sehingga saya bisa memanggil (dalam contoh ini) metode .run ()?
Kamil Kisiel

5
Anda kemudian dapat melakukan getattr (myapp.commands, command) untuk mengakses modul.
Greg Hewgill

1
... atau tambahkan as command_modulepada akhir pernyataan impor dan kemudian lakukancommand_module.run()
oxfn

Dalam beberapa versi Python 3+ exec telah dikonversi menjadi fungsi dengan manfaat tambahan yang dihasilkan oleh sumber referensi dibuat disimpan ke lokal () argumen exec (). Untuk mengisolasi lebih lanjut eksekutif yang direferensikan dari blok kode lokal, Anda dapat memberikan dict Anda sendiri, seperti dict kosong dan merujuk referensi menggunakan dict itu, atau meneruskan dict itu ke fungsi lainnya exec (sumber, gobals (), command1_dict) .... print (command1_dict ['somevarible'])
DevPlayer

2

Mirip dengan solusi @monkut tetapi dapat digunakan kembali dan toleran terhadap kesalahan yang dijelaskan di sini http://stamat.wordpress.com/dynamic-module-import-in-python/ :

import os
import imp

def importFromURI(uri, absl):
    mod = None
    if not absl:
        uri = os.path.normpath(os.path.join(os.path.dirname(__file__), uri))
    path, fname = os.path.split(uri)
    mname, ext = os.path.splitext(fname)

    if os.path.exists(os.path.join(path,mname)+'.pyc'):
        try:
            return imp.load_compiled(mname, uri)
        except:
            pass
    if os.path.exists(os.path.join(path,mname)+'.py'):
        try:
            return imp.load_source(mname, uri)
        except:
            pass

    return mod

0

Bagian di bawah ini berfungsi untuk saya:

>>>import imp; 
>>>fp, pathname, description = imp.find_module("/home/test_module"); 
>>>test_module = imp.load_module("test_module", fp, pathname, description);
>>>print test_module.print_hello();

jika Anda ingin mengimpor skrip shell:

python -c '<above entire code in one line>'

-2

Sebagai contoh, nama modul saya seperti jan_module/ feb_module/ mar_module.

month = 'feb'
exec 'from %s_module import *'%(month)

-7

Berikut ini bekerja untuk saya:

import sys, glob
sys.path.append('/home/marc/python/importtest/modus')
fl = glob.glob('modus/*.py')
modulist = []
adapters=[]
for i in range(len(fl)):
    fl[i] = fl[i].split('/')[1]
    fl[i] = fl[i][0:(len(fl[i])-3)]
    modulist.append(getattr(__import__(fl[i]),fl[i]))
    adapters.append(modulist[i]())

Ini memuat modul dari folder 'modus'. Modul memiliki satu kelas dengan nama yang sama dengan nama modul. Misalnya file mode / modu1.py berisi:

class modu1():
    def __init__(self):
        self.x=1
        print self.x

Hasilnya adalah daftar kelas "adapter" yang dimuat secara dinamis.


13
Tolong jangan mengubur jawaban tanpa memberikan alasan di komentar. Itu tidak membantu siapa pun.
Rebs

Saya ingin tahu mengapa ini hal-hal buruk.
DevPlayer

Sama seperti saya, karena saya baru mengenal Python dan ingin tahu, mengapa ini buruk.
Kosmos

5
Ini buruk bukan hanya karena itu python buruk, tetapi juga itu hanya kode umumnya buruk. Apa fl? Definisi untuk loop terlalu rumit. Jalannya dikodekan dengan keras (dan sebagai contoh, tidak relevan). Ini menggunakan python berkecil hati ( __import__), untuk apa semua itu fl[i]? Ini pada dasarnya tidak dapat dibaca, dan tidak perlu rumit untuk sesuatu yang tidak terlalu sulit - lihat jawaban pilihan teratas dengan one-liner-nya.
Phil
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.