Làm thế nào để nhập một mô-đun cho tên của nó dưới dạng chuỗi?


543

Tôi đang viết một ứng dụng Python lấy lệnh làm đối số, ví dụ:

$ python myapp.py command1

Tôi muốn ứng dụng có thể mở rộng, nghĩa là có thể thêm các mô-đun mới thực hiện các lệnh mới mà không phải thay đổi nguồn ứng dụng chính. Cây trông giống như:

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

Vì vậy, tôi muốn ứng dụng tìm các mô-đun lệnh có sẵn trong thời gian chạy và thực thi mô-đun thích hợp.

Python định nghĩa hàm __import__ , lấy một chuỗi cho tên mô-đun:

__import __ (tên, globalals = Không, địa phương = Không, fromlist = (), level = 0)

Hàm nhập tên mô-đun, có khả năng sử dụng các phần tử và địa phương đã cho để xác định cách diễn giải tên trong ngữ cảnh gói. Danh sách từ đưa ra tên của các đối tượng hoặc mô hình con nên được nhập từ mô-đun được cung cấp theo tên.

Nguồn: https://docs.python.org/3/l Library / fiances.html # import

Vì vậy, hiện tại tôi có một cái gì đó như:

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

command_module.run()

Điều này hoạt động tốt, tôi chỉ tự hỏi liệu có thể có một cách thành ngữ hơn để thực hiện những gì chúng ta đang làm với mã này.

Lưu ý rằng tôi đặc biệt không muốn sử dụng trứng hoặc điểm mở rộng. Đây không phải là một dự án nguồn mở và tôi không hy vọng sẽ có "plugin". Vấn đề là đơn giản hóa mã ứng dụng chính và loại bỏ sự cần thiết phải sửa đổi nó mỗi khi một mô-đun lệnh mới được thêm vào.


Fromlist = ["myapp.commands"] làm gì?
Pieter Müller

@ PieterMüller: trong một lớp vỏ trăn này : dir(__import__). Danh sách từ phải là một danh sách các tên để mô phỏng "từ nhập tên ...".
mawimawi


Kể từ năm 2019, bạn nên tìm kiếm importlib: stackoverflow.com/a/54956419/687896
Brandt

Không sử dụng __import__ xem Python Doc sử dụng importlib.import_module
fcm

Câu trả lời:


321

Với Python cũ hơn 2.7 / 3.1, đó là cách bạn thực hiện nó khá nhiều.

Đối với các phiên bản mới hơn, xem importlib.import_modulecho Python 2 và và Python 3 .

Bạn có thể sử dụng execnếu bạn muốn là tốt.

Hoặc sử dụng, __import__bạn có thể nhập danh sách các mô-đun bằng cách thực hiện việc này:

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

Ripped thẳng từ Lặn vào Python .


7
sự khác biệt để thực hiện là gì?
dùng1767754

1
Làm thế nào bạn có thể sử dụng __init__mô-đun đó?
Dorian Dore

7
Một vấn đề với giải pháp này cho OP là bẫy ngoại lệ cho một hoặc hai mô-đun "lệnh" xấu khiến toàn bộ ứng dụng lệnh của anh ta / cô ta thất bại trong một ngoại lệ. Cá nhân tôi muốn lặp lại cho mỗi lần nhập được gói riêng trong một lần thử: mods = __ import __ () \ nexcept ImportError là lỗi: báo cáo (lỗi) để cho phép các lệnh khác tiếp tục hoạt động trong khi các lệnh xấu được sửa.
DevPlayer

7
Một vấn đề khác với giải pháp này, như Denis Malinovsky chỉ ra dưới đây, là các tài liệu về con trăn tự khuyên không nên sử dụng __import__. 2.7 tài liệu: "Bởi vì chức năng này được sử dụng bởi trình thông dịch Python và không sử dụng chung, nên sử dụng importlib.import_module () ..." Khi sử dụng python3, impmô-đun giải quyết vấn đề này, như monkut đề cập bên dưới.
LiavK

2
@ JohnWu tại sao bạn cần foreachkhi bạn có for element inmap()mặc dù vậy :)
cowbert

300

Cách được đề xuất cho Python 2.7 và 3.1 trở lên là sử dụng importlibmô-đun:

importlib.import_module (tên, gói = Không)

Nhập một mô-đun. Đối số tên chỉ định mô-đun sẽ nhập theo thuật ngữ tuyệt đối hoặc tương đối (ví dụ: pkg.mod hoặc ..mod). Nếu tên được chỉ định theo thuật ngữ tương đối, thì đối số gói phải được đặt thành tên của gói sẽ đóng vai trò là mỏ neo để phân giải tên gói (ví dụ: import_module ('.. mod', 'pkg.subpkg') sẽ nhập pkg.mod).

ví dụ

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

2
Được đề xuất bởi nguồn hoặc thẩm quyền?
michuelnik

66
Tài liệu khuyên chống lại bằng __import__chức năng có lợi cho mô-đun nêu trên.
Denis Malinovsky

8
Điều này hoạt động tốt cho nhập khẩu os.path; làm thế nào về from os.path import *?
Nam G VU

5
Tôi nhận được câu trả lời ở đây stackoverflow.com/a/44492879/248616 tức là. đang gọiglobals().update(my_module.__dict)
Nam G VU

2
@NamGVU, đây là một phương pháp nguy hiểm vì nó gây ô nhiễm toàn cầu của bạn và có thể ghi đè bất kỳ số nhận dạng nào có cùng tên. Liên kết bạn đã đăng có phiên bản cải tiến hơn, tốt hơn của mã này.
Denis Malinovsky

132

Lưu ý: imp không được dùng nữa vì Python 3.4 có lợi cho importlib

Như đã đề cập, mô-đun imp cung cấp cho bạn các chức năng tải:

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

Tôi đã sử dụng những thứ này trước đây để thực hiện một cái gì đó tương tự.

Trong trường hợp của tôi, tôi đã định nghĩa một lớp cụ thể với các phương thức được xác định là bắt buộc. Khi tôi tải mô-đun, tôi sẽ kiểm tra xem lớp có nằm trong mô-đun không, và sau đó tạo một thể hiện của lớp đó, đại loại như sau:

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
Giải pháp tốt và đơn giản. Tôi đã viết một cái tương tự: stamat.wordpress.com/dynamic-module-import-in-python Nhưng bạn có một số sai sót: Còn trường hợp ngoại lệ thì sao? IOError và ImportError? Tại sao không kiểm tra phiên bản đã biên dịch trước rồi đến phiên bản nguồn. Mong đợi một lớp học làm giảm khả năng sử dụng lại trong trường hợp của bạn.
sức chịu đựng

3
Trong dòng nơi bạn xây dựng MyClass trong mô đun đích, bạn sẽ thêm một tham chiếu dự phòng vào tên lớp. Nó đã được lưu trữ expected_classđể bạn có thể làm class_inst = getattr(py_mod,expected_name)()thay thế.
Andrew

1
Lưu ý rằng nếu tồn tại một tệp được biên dịch byte phù hợp (có hậu tố .pyc hoặc .pyo), nó sẽ được sử dụng thay vì phân tích tệp nguồn đã cho . https://docs.python.org/2/l
Library / view.html # view.load_source

1
Cảnh giác: giải pháp này hoạt động; tuy nhiên, mô-đun imp sẽ không được hỗ trợ trong quá trình nhập lib, xem trang của imp: "" "Không dùng nữa kể từ phiên bản 3.4: Gói imp đang chờ khấu hao có lợi cho importlib." ""
Ricardo


15

Ngày nay bạn nên sử dụng importlib .

Nhập tệp nguồn

Các tài liệu thực sự cung cấp một công thức cho điều đó, và nó như sau:

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)

Bằng cách này, bạn có thể truy cập các thành viên (ví dụ: hàm " hello") từ mô-đun của mình pluginX.py- trong đoạn mã này được gọi module- trong không gian tên của nó; Ví dụ module.hello().

Nếu bạn muốn nhập các thành viên (ví dụ: " hello"), bạn có thể bao gồm module/ pluginXtrong danh sách các mô-đun trong bộ nhớ:

sys.modules[module_name] = module

from pluginX import hello
hello()

Nhập một gói

Nhập một gói ( ví dụ , pluginX/__init__.py) theo thư mục hiện tại của bạn thực sự đơn giản:

import importlib

pkg = importlib.import_module('pluginX')

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

3
thêm sys.modules[module_name] = modulevào cuối mã của bạn nếu bạn muốn có thể import module_namesau đó
Daniel Braun

@DanielBraun làm như vậy khiến tôi gặp lỗi> ModuleNotFoundError
Nam G VU

10

Nếu bạn muốn nó ở địa phương của bạn:

>>> 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)]'

tương tự sẽ làm việc với globals()


2
Các tài liệu hướng dẫn cho các built-in locals()chức năng rõ ràng cảnh báo rằng "nội dung của từ điển này không nên được sửa đổi".
martineau

9

Bạn có thể sử dụng exec:

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

Làm thế nào để tôi có được một điều khiển cho mô-đun thông qua exec, để tôi có thể gọi (trong ví dụ này) phương thức .run ()?
Kamil Kisiel

5
Sau đó, bạn có thể thực hiện getattr (myapp.commands, lệnh) để truy cập mô-đun.
Greg Hewgill

1
... hoặc thêm as command_modulevào cuối câu lệnh nhập và sau đó thực hiệncommand_module.run()
oxfn

Trong một số phiên bản của Python 3+ exec đã được chuyển đổi thành một hàm với lợi ích bổ sung mà kết quả được tham chiếu được tạo trong nguồn được lưu trữ vào đối số locals () của exec (). Để tiếp tục cách ly trình thực thi được tham chiếu từ các khối cục bộ mã cục bộ, bạn có thể cung cấp lệnh chính của mình, như một lệnh trống và tham chiếu các tham chiếu bằng cách sử dụng lệnh đó hoặc truyền lệnh đó cho các hàm khác exec (source, gobals (), command1_dict) .... print (lệnh1_dict ['somevarible'])
DevPlayer

3

Tương tự như giải pháp của @monkut nhưng có thể tái sử dụng và chịu lỗi được mô tả ở đây 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

1

Phần dưới đây làm việc cho tôi:

>>>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();

nếu bạn muốn nhập shell-script:

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

-2

Ví dụ, tên mô-đun của tôi giống như jan_module/ feb_module/ mar_module.

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

-7

Sau đây làm việc cho tôi:

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]())

Nó tải các mô-đun từ thư mục 'modus'. Các mô-đun có một lớp duy nhất có cùng tên với tên mô-đun. Ví dụ: tệp modus / modu1.py chứa:

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

Kết quả là một danh sách các "bộ điều hợp" được tải động.


13
Xin đừng chôn câu trả lời mà không cung cấp lý do trong các bình luận. Nó không giúp được ai cả.
Rebs

Tôi muốn biết tại sao đây là thứ xấu.
DevPlayer

Giống như tôi, vì tôi mới dùng Python và muốn biết, tại sao điều này lại tệ.
Kosmos

5
Điều này không chỉ xấu vì nó là con trăn xấu, mà còn nói chung là mã xấu. Là flgì Định nghĩa vòng lặp for là quá phức tạp. Đường dẫn được mã hóa cứng (và ví dụ, không liên quan). Đó là sử dụng python nản lòng ( __import__), tất cả những fl[i]thứ đó để làm gì? Điều này về cơ bản là không thể đọc được và phức tạp không cần thiết đối với một thứ không quá khó - hãy xem câu trả lời được bình chọn hàng đầu với một lớp lót.
Phil
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.