Adı verilen bir modülü dize olarak nasıl alabilirim?


543

Bağımsız değişken olarak komut olarak alan bir Python uygulaması yazıyorum:

$ python myapp.py command1

Uygulamanın genişletilebilir olmasını, yani ana uygulama kaynağını değiştirmek zorunda kalmadan yeni komutlar uygulayan yeni modüller ekleyebilmek istiyorum. Ağaç şuna benzer:

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

Uygulamanın çalışma zamanında mevcut komut modüllerini bulmasını ve uygun olanı çalıştırmasını istiyorum.

Python, bir modül adı için dize alan bir __import__ işlevini tanımlar :

__import __ (ad, globals = Yok, yerliler = Yok, listeden = (), seviye = 0)

İşlev, modül adını alır ve potansiyel olarak adın bir paket bağlamında nasıl yorumlanacağını belirlemek için verilen globalleri ve yerelleri kullanır. Fromlist adıyla verilen modülden içe aktarılması gereken nesnelerin veya alt modüllerin adlarını verir.

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

Yani şu anda bir şey var:

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

command_module.run()

Bu iyi çalışıyor, sadece bu kod ile ne yaptığımızı başarmak için muhtemelen daha deyimsel bir yol olup olmadığını merak ediyorum.

Özellikle yumurta veya uzatma noktası kullanmak istemiyorum. Bu açık kaynaklı bir proje değil ve "eklenti" olmasını beklemiyorum. Mesele, ana uygulama kodunu basitleştirmek ve her yeni komut modülü eklendiğinde kodun değiştirilmesi ihtiyacını ortadan kaldırmaktır.


Fromlist = ["myapp.commands"] ne yapıyor?
Pieter Müller

PieterMüller @ bir piton kabuk, bu tip: dir(__import__). Başlangıç ​​listesi, "ad içe aktarma işleminden ..." ifadesini taklit etmek için bir ad listesi olmalıdır.
mawimawi

3
Dizi

2019'dan itibaren şunlara bakmalısınız importlib: stackoverflow.com/a/54956419/687896
Brandt

__İmport__ kullanmayın bkz. Python Doc bir importlib.import_module
fcm

Yanıtlar:


321

2.7 / 3.1'den daha eski olan Python ile bunu yapıyorsunuz.

Daha yeni sürümler importlib.import_moduleiçin bkz . Python 2 ve Python 3 .

İsterseniz de kullanabilirsiniz exec.

Veya bunu kullanarak __import__bir modül listesi içe aktarabilirsiniz:

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

Dalış içine Python doğruca sökük .


7
execece farkı nedir?
user1767754

1
__init__Bu modülü nasıl kullanabilirsiniz ?
Dorian Dore

7
OP için bu çözümle ilgili bir sorun, bir veya iki hatalı "komut" modülü için özel durumları yakalamanın, tüm komut uygulamasını bir istisnada başarısız kılmasıdır. Şahsen ben denemek tek tek sarılmış her ithalat üzerinde döngü için : mods = __ import __ () \ nexex ImportError hata olarak: rapor (hata) kötü komutlar düzeltilirken çalışmaya devam etmek için izin verir.
DevPlayer

7
Denis Malinovsky'nin aşağıda belirttiği gibi bu çözümle ilgili bir başka sorun, python belgelerinin kendilerinin kullanılmamasını tavsiye etmeleridir __import__. 2.7 dokümanlar: "Bu işlev Python yorumlayıcısı tarafından kullanılmak üzere tasarlandığından ve genel kullanım için olmadığından importlib.import_module () kullanmak daha iyidir." Python3 kullanırken, impmodül bu soruyu aşağıda belirtilen şekilde çözmektedir.
LiavK

2
@JohnWu neden ihtiyacın var foreachsen varken for element inve map():) gerçi
cowbert

300

Python 2.7 ve 3.1 ve sonraki sürümleri için önerilen yöntem importlibmodülü kullanmaktır :

importlib.import_module (ad; paket = Yok)

Bir modülü içe aktarın. Name argümanı, hangi modülün mutlak veya göreceli olarak içe aktarılacağını belirtir (örn. Pkg.mod veya ..mod). Ad göreli terimlerle belirtilirse, paket bağımsız değişkeninin paket adını çözümlemek için bağlantı noktası olarak işlev görecek paket adı olarak ayarlanması gerekir (örneğin import_module ('.. mod', 'pkg.subpkg') pkg.mod ithal edecek).

Örneğin

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

2
Hangi kaynak veya otorite tarafından önerilir?
michuelnik

66
Belgeler işlevi yukarıda belirtilen modül lehine kullanmaya karşı tavsiyelerde bulunur __import__.
Denis Malinovsky

8
Bu ithalat için iyi çalışır os.path; nasıl from os.path import *?
Nam G VU

5
Cevabı buradan alıyorum stackoverflow.com/a/44492879/248616 ie. aramaglobals().update(my_module.__dict)
Nam G VU

2
@NamGVU, bu tehlikeli bir yöntemdir çünkü globalleri kirletir ve aynı adlara sahip tanımlayıcıları geçersiz kılabilir. Gönderdiğiniz bağlantı, bu kodun daha iyi ve geliştirilmiş bir sürümüne sahip.
Denis Malinovsky

132

Not: imp lehine Python 3.4 beri kullanımdan kaldırıldı importlib

Belirtildiği gibi imp modülü size yükleme fonksiyonları sağlar:

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

Bunları daha önce benzer bir şey yapmak için kullandım.

Benim durumumda, gerekli tanımlı yöntemlerle belirli bir sınıf tanımladım. Modülü yükledikten sonra sınıfın modülde olup olmadığını kontrol eder ve daha sonra bu sınıfın bir örneğini yaratırım:

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
İyi ve basit bir çözüm. Benzer bir tane yazdık : stamat.wordpress.com/dynamic-module-import-in-python Ama seninkinin bazı kusurları var: İstisnalar ne olacak? IOError ve ImportError? Neden önce derlenmiş sürümü, sonra da kaynak sürümü kontrol etmiyorsunuz. Bir sınıf beklemek, sizin durumunuzdaki yeniden kullanılabilirliği azaltır.
stamat

3
Hedef modülde MyClass'ı oluşturduğunuz satıra, sınıf adına yedekli bir başvuru ekliyorsunuz. Zaten depolanmış, bunun yerine expected_classbunu yapabilirsiniz class_inst = getattr(py_mod,expected_name)().
Andrew

1
Doğru şekilde eşleşen bir bayt derlemeli dosya (.pyc veya .pyo sonekiyle birlikte) varsa, verilen kaynak dosyayı ayrıştırmak yerine kullanılacağını unutmayın . https://docs.python.org/2/library/imp.html#imp.load_source
cdosborn

1
Dikkat: bu çözüm işe yarar; ancak, imp modülü import lib lehine kullanımdan kaldırılacak, imp sayfasına bakın: "" "3.4 sürümünden beri kullanımdan kaldırıldı: Imp paketi importlib lehine kullanımdan kaldırılıyor." ""
Ricardo


15

Bugünlerde importlib kullanmalısınız .

Kaynak dosyayı içe aktarma

Docs aslında bunun için bir reçete sunmak ve bu böyle gider:

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)

Bu şekilde üyelere (örn., " hello" İşlevi ) modülünüzden pluginX.py- çağrılan bu snippet'te module- ad alanının altında erişebilirsiniz ; Ör module.hello().

Üyeleri (örn. " hello") İçe aktarmak istiyorsanız, bellek içi modül listesine module/ ekleyebilirsiniz pluginX:

sys.modules[module_name] = module

from pluginX import hello
hello()

Bir paketi içe aktarma

Geçerli dizininiz altında bir paketi ( ör . pluginX/__init__.py) İçe aktarmak aslında oldukça basittir:

import importlib

pkg = importlib.import_module('pluginX')

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

3
daha sonra sys.modules[module_name] = moduleyapabilmek istiyorsanız kodunuzun sonuna ekleyinimport module_name
Daniel Braun

@DanielBraun bunu yapmak beni hata al> ModuleNotFoundError
Nam G VU

11

Yerlilerinizde istiyorsanız:

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

aynı şekilde çalışmak globals()


Dokümantasyon için dahili içinde locals()işlevi açıkça "bu sözlükte içeriği değiştirilmemelidir" konusunda uyarıyor.
martineau

9

Şunları kullanabilirsiniz exec:

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

(Bu örnekte) .run () yöntemini çağırabilmem için, exec üzerinden modülün tanıtıcısını nasıl alabilirim?
Kamil Kisiel

5
Daha sonra modüle erişmek için getattr (myapp.commands, command) yapabilirsiniz.
Greg Hewgill

1
... veya as command_moduleiçe aktarma ifadesinin sonuna ekleyin ve sonra yapıncommand_module.run()
oxfn

Python 3+ exec'nin bazı versiyonlarında, kaynakta yaratılan referansın, exec () argümanının locals () argümanına kaydedildiği ilave faydaya sahip bir fonksiyona dönüştürülmüştür. Yürütülen yerel kod bloğu yerlilerinden daha fazla izole etmek için boş bir adres gibi kendi diktenizi sağlayabilir ve bu dikteyi kullanarak referanslara başvurabilir veya bu dikteyi exec (source, gobals (), command1_dict) işlevlerine iletebilirsiniz. .... print (command1_dict ['somevarible'])
DevPlayer

3

@Monkut'un çözümüne benzer, ancak burada açıklanan yeniden kullanılabilir ve hataya dayanıklılık 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

Aşağıdaki parça benim için çalıştı:

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

kabuk betiğinde içe aktarmak istiyorsanız:

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

-2

Örneğin, modül isimlerim jan_module/ feb_module/ gibidir mar_module.

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

-7

Aşağıdakiler benim için çalıştı:

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

Modülleri 'modus' klasöründen yükler. Modüller, modül adıyla aynı ada sahip tek bir sınıfa sahiptir. Örneğin modus / modu1.py dosyası şunları içerir:

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

Sonuç, dinamik olarak yüklenen sınıf "bağdaştırıcıları" listesidir.


13
Lütfen yorumlarda bir neden belirtmeden cevapları gömmeyin. Kimseye yardım etmez.
Rebs

Bunun neden kötü şeyler olduğunu bilmek istiyorum.
DevPlayer

Benimle aynı, Python'da yeni olduğumdan ve bunun neden kötü olduğunu bilmek istediğimden.
Kosmos

5
Bu sadece kötü python olduğu için değil, aynı zamanda genellikle kötü kod olduğu için de kötüdür. Nedir fl? For döngüsü tanımı aşırı derecede karmaşıktır. Yol sabit olarak kodlanmıştır (ve örneğin, ilgisiz). Cesareti kırılmış python ( __import__) kullanıyor, bunun için ne var fl[i]? Bu temelde okunamaz ve o kadar da zor olmayan bir şey için gereksiz yere karmaşıktır - tek astarlı en iyi oylanan cevaba bakın.
Phil
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.