วิธีการนำเข้าโมดูลที่ตั้งชื่อเป็นสตริง?


543

ฉันกำลังเขียนแอปพลิเคชัน Python ที่ใช้เป็นคำสั่งเป็นอาร์กิวเมนต์ตัวอย่างเช่น:

$ python myapp.py command1

ฉันต้องการให้แอปพลิเคชันสามารถขยายได้นั่นคือเพื่อให้สามารถเพิ่มโมดูลใหม่ที่ใช้คำสั่งใหม่โดยไม่ต้องเปลี่ยนแหล่งแอพพลิเคชันหลัก ต้นไม้ดูเหมือนว่า:

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

ดังนั้นฉันต้องการให้แอปพลิเคชันค้นหาโมดูลคำสั่งที่มีอยู่ตอนรันไทม์และดำเนินการโมดูลที่เหมาะสม

Python กำหนดฟังก์ชั่น__import__ซึ่งรับสตริงสำหรับชื่อโมดูล:

__import __ (ชื่อ, globals = None, locals = None, fromlist = (), level = 0)

ฟังก์ชันนำเข้าชื่อโมดูลซึ่งอาจใช้ globals และ locals ที่กำหนดเพื่อกำหนดวิธีตีความชื่อในบริบทแพ็คเกจ รายการจากให้ชื่อของวัตถุหรือ submodules ที่ควรจะนำเข้าจากโมดูลที่กำหนดโดยชื่อ

ที่มา: https://docs.python.org/3/library/functions.html# นำเข้า

ดังนั้นในปัจจุบันฉันมีสิ่งที่ชอบ:

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

command_module.run()

มันใช้งานได้ดีฉันแค่สงสัยว่าอาจมีวิธีที่เป็นไปได้มากกว่าในการทำสิ่งที่เราทำกับรหัสนี้

โปรดทราบว่าฉันไม่ต้องการเข้าใช้ไข่หรือจุดส่วนขยายโดยเฉพาะ นี่ไม่ใช่โครงการโอเพ่นซอร์สและฉันไม่คาดหวังว่าจะมี "ปลั๊กอิน" จุดประสงค์เพื่อลดความซับซ้อนของรหัสแอปพลิเคชันหลักและลบความจำเป็นในการแก้ไขในแต่ละครั้งที่มีการเพิ่มโมดูลคำสั่งใหม่


fromlist = ["myapp.commands"] ทำอะไร?
Pieter Müller

@ PieterMüller: dir(__import__)ในเปลือกหลามพิมพ์นี้: รายการจากควรเป็นรายชื่อที่จะเลียนแบบ "จากการนำเข้าชื่อ ... "
mawimawi


ตั้งแต่ 2019 คุณควรค้นหาimportlib: stackoverflow.com/a/54956419/687896
แบรนด์

อย่าใช้ __import__ เห็นงูหลามหมอ ใช้importlib.import_module
FCM

คำตอบ:


321

ด้วย Python ที่มีอายุมากกว่า 2.7 / 3.1 นั่นเป็นวิธีที่คุณทำได้

สำหรับรุ่นใหม่ดูimportlib.import_moduleสำหรับงูหลาม 2และและงูหลาม 3

คุณสามารถใช้execถ้าคุณต้องการเช่นกัน

หรือใช้__import__คุณสามารถนำเข้ารายการของโมดูลโดยทำสิ่งนี้:

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

ฉีกตรงจากดำน้ำลงในหลาม


7
differece ที่จะ exec คืออะไร?
1767754

1
คุณจะใช้__init__โมดูลนั้นได้อย่างไร?
Dorian Dore

7
ปัญหาอย่างหนึ่งของการแก้ปัญหานี้สำหรับ OP คือการยกเว้นการดักจับสำหรับหนึ่งหรือสองโมดูล "คำสั่ง" ที่ไม่ดีทำให้แอปคำสั่งทั้งหมดของเขา / เธอล้มเหลวในข้อยกเว้นหนึ่งข้อ โดยส่วนตัวฉันจะวนซ้ำการอิมพอร์ตแต่ละรายการในการลอง: mods = __ อิมพอร์ต __ () \ n ยกเว้น ImportError เป็นข้อผิดพลาด: รายงาน (ข้อผิดพลาด) เพื่ออนุญาตให้คำสั่งอื่นทำงานต่อไปในขณะที่คนไม่ดีได้รับการแก้ไข
DevPlayer

7
ปัญหาเกี่ยวกับการแก้ปัญหานี้ก็เป็นเดนิส Malinovsky __import__ชี้ให้เห็นด้านล่างคือว่าเอกสารหลามตัวเองแนะนำไม่ได้ใช้ 2.7 docs: "เนื่องจากฟังก์ชั่นนี้มีไว้สำหรับใช้งานโดย Python interpreter และไม่ใช่สำหรับการใช้งานทั่วไปมันจะดีกว่าที่จะใช้ importlib.import_module () ... " เมื่อใช้ python3 impโมดูลจะแก้ไขปัญหานี้ตามที่ monkut ระบุไว้ด้านล่าง
LiavK

2
@JohnWu ทำไมคุณต้องการforeachเมื่อคุณมีfor element inและmap()แม้ว่า :)
cowbert

300

วิธีที่แนะนำสำหรับ Python 2.7 และ 3.1 และใหม่กว่าคือการใช้importlibโมดูล:

importlib.import_module (ชื่อ, แพ็คเกจ = ไม่มี)

นำเข้าโมดูล อาร์กิวเมนต์ name ระบุโมดูลที่จะนำเข้าในแบบสัมบูรณ์หรือคำที่เกี่ยวข้อง (เช่น pkg.mod หรือ ..mod) หากชื่อมีการระบุไว้ในคำที่เกี่ยวข้องแล้วอาร์กิวเมนต์แพคเกจจะต้องตั้งค่าเป็นชื่อของแพคเกจที่จะทำหน้าที่เป็นจุดยึดสำหรับการแก้ไขชื่อแพคเกจ (เช่น import_module ('.. mod', 'pkg.subpkg') จะนำเข้า pkg.mod)

เช่น

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

2
แนะนำโดยแหล่งที่มาหรือหน่วยงานใด
michuelnik

66
เอกสารแนะนำให้ต่อต้านการใช้__import__ฟังก์ชั่นแทนโมดูลที่กล่าวถึงข้างต้น
เดนิสมาลินอฟสกี้

8
นี้ทำงานได้ดีสำหรับการนำเข้าos.path; แล้วไงfrom os.path import *ล่ะ
Nam G VU

5
ฉันได้รับคำตอบที่นี่stackoverflow.com/a/44492879/248616เช่น โทรglobals().update(my_module.__dict)
Nam G VU

2
@NamGVU นี่เป็นวิธีที่อันตรายเพราะมันสร้างมลภาวะให้กับคุณและสามารถแทนที่ตัวระบุใด ๆ ด้วยชื่อเดียวกัน ลิงค์ที่คุณโพสต์มีโค้ดนี้ที่ดีขึ้นและได้รับการปรับปรุง
เดนิสมาลินอฟสกี้

132

หมายเหตุ: impถูกเลิกใช้ตั้งแต่ Python 3.4 ในความโปรดปรานของimportlib

ตามที่กล่าวไว้ในโมดูลimpให้คุณโหลดฟังก์ชัน:

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

ฉันเคยใช้สิ่งเหล่านี้ก่อนที่จะทำสิ่งที่คล้ายกัน

ในกรณีของฉันฉันกำหนดคลาสเฉพาะด้วยวิธีการที่กำหนดที่จำเป็น เมื่อฉันโหลดโมดูลฉันจะตรวจสอบว่าคลาสนั้นอยู่ในโมดูลหรือไม่จากนั้นสร้างอินสแตนซ์ของคลาสนั้นเป็นดังนี้:

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
ทางออกที่ดีและเรียบง่าย ฉันเขียนสิ่งที่คล้ายกัน: stamat.wordpress.com/dynamic-module-import-in-pythonแต่ของคุณมีข้อบกพร่องบางอย่าง: ข้อยกเว้น? IOError และ ImportError? ทำไมไม่ตรวจสอบเวอร์ชั่นที่คอมไพล์ก่อนแล้วค่อยไปหาเวอร์ชั่นต้นฉบับ การคาดหวังว่าชั้นเรียนจะช่วยลดการใช้ซ้ำในกรณีของคุณ
Stamat

3
ในบรรทัดที่คุณสร้าง MyClass ในโมดูลเป้าหมายคุณกำลังเพิ่มการอ้างอิงที่ซ้ำซ้อนกับชื่อคลาส จะถูกเก็บไว้แล้วในexpected_classเพื่อให้คุณสามารถทำclass_inst = getattr(py_mod,expected_name)()แทน
แอนดรู

1
โปรดทราบว่าถ้าไฟล์ไบต์รวบรวมถูกจับคู่ (มีคำต่อท้าย .pyc หรือ .pyo) ที่มีอยู่ก็จะถูกนำมาใช้แทนการแยกไฟล์ที่มารับ https://docs.python.org/2/library/imp.html#imp.load_source
cdosborn

1
หัวขึ้น: วิธีนี้ใช้ได้ผล; อย่างไรก็ตามโมดูล imp จะเลิกใช้ในการนำเข้า lib ดูที่หน้าของ imp: "" "เลิกใช้แล้วตั้งแต่เวอร์ชัน 3.4: แพ็กเกจ imp กำลังรอการคัดค้านเพื่อนำเข้า libl" ""
Ricardo


15

ปัจจุบันคุณควรใช้importlib

นำเข้าไฟล์ต้นฉบับ

เอกสารจริงให้สูตรสำหรับที่และมันจะเป็นเช่น:

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)

วิธีนี้คุณสามารถเข้าถึงสมาชิก (เช่นฟังก์ชั่น " hello") จากโมดูลของคุณpluginX.py- ในข้อมูลโค้ดนี้จะถูกเรียกmodule- ภายใต้เนมสเปซ เช่นmodule.hello().

หากคุณต้องการนำเข้าสมาชิก (เช่น " hello") คุณสามารถรวมmodule/ pluginXในรายการโมดูลหน่วยความจำใน:

sys.modules[module_name] = module

from pluginX import hello
hello()

นำเข้าแพ็คเกจ

การนำเข้าแพคเกจ ( เช่น , pluginX/__init__.py) ภายใต้ dir ปัจจุบันของคุณเป็นจริงตรงไปตรงมา:

import importlib

pkg = importlib.import_module('pluginX')

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

3
เพิ่มsys.modules[module_name] = moduleไปยังจุดสิ้นสุดของรหัสของคุณถ้าคุณต้องการที่จะสามารถในimport module_nameภายหลัง
Daniel Braun

@DanielBraun ทำเช่นนั้นให้ฉันเกิดข้อผิดพลาด> ModuleNotFoundError
Nam G VU

11

หากคุณต้องการในท้องถิ่นของคุณ:

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

เดียวกันจะทำงานกับ globals()


เอกสารสำหรับในตัวlocals()ฟังก์ชั่นอย่างชัดเจนเตือนว่า "เนื้อหาของพจนานุกรมนี้ไม่ควรได้รับการแก้ไข"
martineau

9

คุณสามารถใช้exec:

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

ฉันจะได้รับหมายเลขอ้างอิงไปยังโมดูลผ่านทาง exec เพื่อให้ฉันสามารถเรียก (ในตัวอย่างนี้) วิธีการ. run () ได้อย่างไร
Kamil Kisiel

5
จากนั้นคุณสามารถทำ getattr (myapp.commands, command) เพื่อเข้าถึงโมดูล
Greg Hewgill

1
... หรือเพิ่มas command_moduleในตอนท้ายของคำแถลงการนำเข้าจากนั้นทำcommand_module.run()
oxfn

ในบางเวอร์ชันของ Python 3+ execถูกแปลงเป็นฟังก์ชั่นพร้อมสิทธิประโยชน์เพิ่มเติมที่ผลลัพธ์อ้างอิงที่สร้างขึ้นในแหล่งข้อมูลจะถูกเก็บไว้ในอาร์กิวเมนต์ local () ของ exec () หากต้องการแยกผู้บริหารที่อ้างอิงจากโลคอลบล็อครหัสท้องถิ่นเพิ่มเติมคุณสามารถระบุ dict ของคุณเองเช่นเดียวกับที่ว่างเปล่าและอ้างอิงการอ้างอิงโดยใช้ dict นั้นหรือส่งผ่าน dict นั้นไปยังฟังก์ชันอื่น ๆ exec (source, gobals (), command1_dict) .... พิมพ์ (command1_dict ['somevarible'])
DevPlayer

3

คล้ายกับโซลูชันของ @monkut แต่สามารถนำมาใช้ซ้ำได้และมีข้อผิดพลาดที่อธิบายได้ที่นี่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

ชิ้นด้านล่างใช้ได้สำหรับฉัน:

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

หากคุณต้องการนำเข้าในเชลล์สคริปต์:

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

-2

ยกตัวอย่างเช่นชื่อโมดูลของฉันเป็นเหมือนjan_module/ /feb_modulemar_module

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

-7

การทำงานต่อไปนี้สำหรับฉัน:

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

มันโหลดโมดูลจากโฟลเดอร์ 'modus' โมดูลมีคลาสเดียวที่มีชื่อเดียวกันกับชื่อโมดูล เช่นไฟล์ modus / modu1.py ประกอบด้วย:

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

ผลลัพธ์คือรายการของ "อะแดปเตอร์" คลาสที่โหลดแบบไดนามิก


13
กรุณาอย่าฝังคำตอบโดยไม่ให้เหตุผลในความคิดเห็น มันไม่ได้ช่วยใคร
Rebs

ฉันต้องการที่จะรู้ว่าทำไมสิ่งนี้เป็นสิ่งที่ไม่ดี
DevPlayer

เช่นเดียวกับฉันเนื่องจากฉันใหม่กับ Python และต้องการทราบว่าทำไมสิ่งนี้ไม่ดี
Kosmos

5
สิ่งนี้ไม่ดีไม่เพียงเพราะมันเป็นงูหลามที่เลวร้าย แต่ยังเป็นเพียงรหัสที่ไม่ดี คือflอะไร for definition definition มีความซับซ้อนมากเกินไป เส้นทางมีการเข้ารหัสยาก (และสำหรับตัวอย่างที่ไม่เกี่ยวข้อง) มันใช้หลามท้อใจ ( __import__) สิ่งที่มีไว้fl[i]เพื่ออะไร? สิ่งนี้ไม่สามารถอ่านได้โดยทั่วไปและมีความซับซ้อนเกินความจำเป็นสำหรับบางสิ่งที่ไม่ยาก - ดูคำตอบที่ได้รับการโหวตสูงสุดด้วยซับเดี่ยว
Phil
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.