Bir klasöre tüm modüller nasıl yüklenir?


269

Birisi bana bütün bir modül dizini almanın iyi bir yolunu sağlayabilir mi?
Ben böyle bir yapı var:

/Foo
    bar.py
    spam.py
    eggs.py

Sadece ekleyerek __init__.pyve yaparak bir pakete dönüştürmeyi denedim from Foo import *ama umduğum şekilde çalışmadı.


2
İnit .py yolu oldukça doğrudur. Çalışmayan kodu gönderebilir misiniz?
balpha

9
"İşe yaramadı" tanımlayabilir misiniz? Ne oldu? Hangi hata mesajını aldınız?
S.Lott

Bu Pythonic veya tavsiye edilir mi? "Açık, örtük olmaktan iyidir."
yangmillstheory

Açık gerçekten daha iyidir. Ancak, her yeni modül eklediğinizde bu can sıkıcı 'bulunamadı' mesajlarına gerçekten değinmek ister misiniz? Ben birçok küçük modül fonksiyonları içeren bir paket dizininiz varsa, o zaman bu konuda gitmek için en güzel yol olduğunu düşünüyorum. Benim varsayımlarım: 1. modül oldukça basit 2. Sen kod düzen için paketi kullanın
Ido_f

Yanıtlar:


400

.pyGeçerli klasördeki tüm python ( ) dosyalarını listele ve __all__değişken olarak__init__.py

from os.path import dirname, basename, isfile, join
import glob
modules = glob.glob(join(dirname(__file__), "*.py"))
__all__ = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')]

57
Temelde bu yüzden başka bir yapılandırma ile bir dizine python dosyaları bırakabilirsiniz ve başka bir yerde çalışan bir komut dosyası tarafından yürütülmesini.
Evan Fosmark

5
@NiallDouglas bu cevap OP'nin sorduğu belirli bir soru için, bir zip dosyası yoktu ve pyc dosyaları kolayca dahil edilebilir ve .pyd veya .so libs vb. De unutuyorsun
Anurag Uniyal

35
Ekleyeceğim tek şey if not os.path.basename(f).startswith('_')ya da en azından if not f.endswith('__init__.py')liste kavrayışının sonuna
Pykler

10
Daha sağlam hale getirmek için ayrıca emin os.path.isfile(f)olduğunu True. Bu gibi kırık semboller ve dizinleri filtre somedir.py/(köşe-case, itiraf, ama yine de ...)
MestreLion

12
Ekle from . import *ayarında sonra __all__size yardımcı modüllerle kullanarak kullanılabilir olmasını istiyorsanız .(örn olarak module.submodule1, module.submodule2vs.).
ostrokach

133

__all__Değişkeni __init__.pyiçerenlere ekleyin :

__all__ = ["bar", "spam", "eggs"]

Ayrıca bkz. Http://docs.python.org/tutorial/modules.html


163
Evet, evet, ama dinamik olmasını sağlamanın bir yolu var mı?
Evan Fosmark

27
os.listdir()Bazı filtreleme, .pyuzatma ve sıyırma kombinasyonu __all__.

Burada benim için örnek kod olarak çalışmıyor github.com/namgivu/python-import-all/blob/master/error_app.py . Belki orada bir şey özledim?
Nam G VU

1
Kendimi öğrendim - bu modüllerde tanımlanan değişkeni / nesneyi kullanmak için tam referans yolunu kullanmalıyız, örneğin moduleName.varNameref. stackoverflow.com/a/710603/248616
Nam G VU

1
@NamGVU: Bu kod Cevabıma ilgili soruya paketin ad içine tüm kamu alt modüllerin isimlerini ithal edecek.
martineau

54

2017'de güncelleme: muhtemelen importlibbunun yerine kullanmak istersiniz .

Bir ekleyerek Foo dizinini bir paket yapın __init__.py. Bu __init__.pyekte:

import bar
import eggs
import spam

Dinamik olmasını istediğinizden (iyi bir fikir olabilir veya olmayabilir), tüm py dosyalarını list dir ile listeleyip aşağıdaki gibi bir şeyle içe aktarın:

import os
for module in os.listdir(os.path.dirname(__file__)):
    if module == '__init__.py' or module[-3:] != '.py':
        continue
    __import__(module[:-3], locals(), globals())
del module

Ardından, kodunuzdan bunu yapın:

import Foo

Artık modüllere

Foo.bar
Foo.eggs
Foo.spam

vs. from Foo import *, isim çakışmaları ve kodun analizini zorlaştırmak gibi çeşitli nedenlerle iyi bir fikir değildir.


1
Kötü değil, ancak .pyc ve .pyo dosyalarını da içe aktarabileceğinizi unutmayın.
Evan Fosmark

7
tbh, ben __import__hackish buluyorum , bence isimleri eklemek __all__ve daha sonra from . import *komut dosyasının altına koymak daha iyi olurdu
freeforall tousez

1
Bunun glob versiyonundan daha güzel olduğunu düşünüyorum.
lpapp

8
__import__Genel kullanımlar için, bu tarafından kullanılmaz interpreter, kullanmak importlib.import_module()yerine.
Andrew_1510

2
İlk örnek çok yardımcı oldu, teşekkürler! Python 3.6.4 altında, Python'un içe aktarılmasından önce from . import eggsvb. Yapmak zorunda kaldım __init__.py. Yalnızca ile import eggsalıyorum ModuleNotFoundError: No module named 'eggs'çalışırken import Foode main.pyyukarıdaki dizinde.
Nick

41

Mihail'in cevabını genişleterek, hackish olmayan yolun (dosya yollarını doğrudan işlememe gibi) aşağıdaki olduğuna inanıyorum:

  1. __init__.pyaltında boş bir dosya oluşturFoo/
  2. gerçekleştirmek
import pkgutil
import sys


def load_all_modules_from_dir(dirname):
    for importer, package_name, _ in pkgutil.iter_modules([dirname]):
        full_package_name = '%s.%s' % (dirname, package_name)
        if full_package_name not in sys.modules:
            module = importer.find_module(package_name
                        ).load_module(full_package_name)
            print module


load_all_modules_from_dir('Foo')

Alacaksınız:

<module 'Foo.bar' from '/home/.../Foo/bar.pyc'>
<module 'Foo.spam' from '/home/.../Foo/spam.pyc'>

Doğru cevaba giden yol budur - ZIP arşivlerini işler, ancak init veya import yazmaz . Aşağıdaki otomatiğe bakın.
Niall Douglas

1
Başka bir şey: yukarıdaki örnek, modülün zaten yüklü olup olmadığını görmek için sys.modules'ü kontrol etmez. Bu olmadan yukarıdaki modül ikinci kez yükleyecektir kontrol edin :)
Niall Douglas

3
Kodunuzla load_all_modules_from_dir ('Foo / bar') komutunu çalıştırdığımda "RuntimeWarning: Ana modül 'Foo / bar' bulunamadı mutlak içe aktarma" - bunu bastırmak için, full_package_name = 'ayarlamalıyım.'. dirname.split (os.path.sep) + package_name]) ve ayrıca Foo.bar dosyasını içe aktarın
Alex Dupuy

Bu RuntimeWarningmesajlar da hiç full_package_name kullanmayan önlenebilir: importer.find_module(package_name).load_module(package_name).
Artfunkel

RuntimeWarningHatalar da ebeveyn (AKA dizinadı) ithal ederek (bir tartışmalı çirkin şekilde) önlenebilir. Bunu yapmanın bir yolu - if dirname not in sys.modules: pkgutil.find_loader(dirname).load_module(dirname). Tabii ki, bu sadece dirnametek bileşenli göreli bir yol ise işe yarar ; eğik çizgi yok. Şahsen, @ Artfunkel'in bunun yerine base_name temel paketini kullanma yaklaşımını tercih ediyorum.
dannysauer

31

Python, bir dizinin altındaki tüm dosyaları dahil et:

Ellerini tutması gereken işe yaramayan yeni başlayanlar için.

  1. Bir klasör / home / el / foo yapın ve main.py/ home / el / foo altında bir dosya oluşturun. Bu kodu buraya yerleştirin:

    from hellokitty import *
    spam.spamfunc()
    ham.hamfunc()
    
  2. Bir dizin oluşturun /home/el/foo/hellokitty

  3. __init__.pyAltında bir dosya oluşturun /home/el/foo/hellokittyve bu kodu buraya yerleştirin:

    __all__ = ["spam", "ham"]
  4. İki python dosyası oluşturun: spam.pyve ham.pyaltı/home/el/foo/hellokitty

  5. Spam.py içinde bir işlev tanımlayın:

    def spamfunc():
      print("Spammity spam")
    
  6. Ham.py içinde bir işlev tanımlayın:

    def hamfunc():
      print("Upgrade from baloney")
    
  7. Çalıştır:

    el@apollo:/home/el/foo$ python main.py 
    spammity spam
    Upgrade from baloney
    

1
Sadece yeni başlayanlar için değil, aynı zamanda net cevapları seven Python geliştiricileri de deneyimledi. Teşekkürler.
Michael Scheper

Ancak import *kullanımı kötü Python kodlama uygulaması olarak kabul edilir. Bunu onsuz nasıl yapıyorsun?
Rachael Blake

16

Bu sorundan kendim bıktım, bu yüzden düzeltmek için automodinit adlı bir paket yazdım. Http://pypi.python.org/pypi/automodinit/ adresinden edinebilirsiniz .

Kullanımı şöyle:

  1. automodinitPaketi setup.pybağımlılıklarınıza dahil edin .
  2. Tüm __init__.py dosyalarını şu şekilde değiştirin:
__all__ = ["Yeniden yazacağım"]
# Yukarıdaki satırı veya bu satırı değiştirmeyin!
içe aktarma otomatiği
automodinit.automodinit (__ name__, __file__, globaller ())
del automodinit
# İstediğiniz başka bir şey buradan sonra gidebilir, değiştirilmez.

Bu kadar! Şu andan itibaren bir modül içe aktarıldığında __all__ modüldeki .py [co] dosyaları listesine ayarlanacak ve bu dosyaların her biri siz yazmışsınız gibi içe aktarılacaktır:

for x in __all__: import x

Bu nedenle, "M içe aktarımından *" etkisi tam olarak "içe aktarma M" ile eşleşir.

automodinit ZIP arşivlerinin içinden koşmaktan mutlu olduğu için ZIP güvenlidir.

Niall


pip, automodinit'i indiremez, çünkü bunun için pypi'ye yüklenmiş bir şey yoktur.
kanzure

Github ile ilgili hata raporu için teşekkürler. Bunu v0.13'te düzelttim. Niall
Niall Douglas

11

Oldukça eski bir automodinitgönderiyi güncellediğimi biliyorum ve kullanmayı denedim , ancak kurulum işleminin python3 için bozulduğunu öğrendim. Bu nedenle, Luca'nın cevabına dayanarak, bu konuya .zip ile çalışmayabilir - daha basit bir cevap buldum, bu yüzden burada paylaşmam gerektiğini düşündüm:

__init__.pymodül içinde yourpackage:

#!/usr/bin/env python
import os, pkgutil
__all__ = list(module for _, module, _ in pkgutil.iter_modules([os.path.dirname(__file__)]))

ve aşağıdaki başka bir paket içinde yourpackage:

from yourpackage import *

Daha sonra paketin içine yerleştirilen tüm modüller yüklenir ve yeni bir modül yazarsanız, otomatik olarak da içe aktarılır. Tabii ki, bu tür şeyleri dikkatle kullanın, büyük güçlerle büyük sorumluluklar gelir.


6
import pkgutil
__path__ = pkgutil.extend_path(__path__, __name__)
for imp, module, ispackage in pkgutil.walk_packages(path=__path__, prefix=__name__+'.'):
  __import__(module)

5

Ayrıca bu sorunla karşılaştım ve bu benim çözümüm oldu:

import os

def loadImports(path):
    files = os.listdir(path)
    imps = []

    for i in range(len(files)):
        name = files[i].split('.')
        if len(name) > 1:
            if name[1] == 'py' and name[0] != '__init__':
               name = name[0]
               imps.append(name)

    file = open(path+'__init__.py','w')

    toWrite = '__all__ = '+str(imps)

    file.write(toWrite)
    file.close()

Bu işlev, klasördeki her modülü tutan __init__.pybir __all__değişken içeren bir dosya (sağlanan klasörde) oluşturur .

Örneğin, adında bir klasör var Test :

Foo.py
Bar.py

Komut dosyasında modüllerin içe aktarılmasını istiyorum:

loadImports('Test/')
from Test import *

Bu, her şeyi içeri aktarır Testve içindeki __init__.pydosya Testşimdi şunları içerir:

__all__ = ['Foo','Bar']

mükemmel, tam olarak ne arıyorum
uma mahesh

4

Anurag'ın birkaç düzeltmeyle örneği:

import os, glob

modules = glob.glob(os.path.join(os.path.dirname(__file__), "*.py"))
__all__ = [os.path.basename(f)[:-3] for f in modules if not f.endswith("__init__.py")]

4

Önerilen iyileştirmelerle Anurag Uniyal cevabı !

#!/usr/bin/python
# -*- encoding: utf-8 -*-

import os
import glob

all_list = list()
for f in glob.glob(os.path.dirname(__file__)+"/*.py"):
    if os.path.isfile(f) and not os.path.basename(f).startswith('_'):
        all_list.append(os.path.basename(f)[:-3])

__all__ = all_list  

3

Senin o Bkz __init__.pytanımlayıp __all__. Modülleri - paketler doc diyor

__init__.pyDosyaları Python tedavi paket içeren dizinleri yapmaları gerekmektedir; bu, dize gibi ortak bir ada sahip dizinlerin, daha sonra modül arama yolunda gerçekleşen geçerli modülleri yanlışlıkla gizlemesini önlemek için yapılır. En basit durumda, __init__.pyboş bir dosya olabilir, ancak paket için başlatma kodunu yürütebilir veya __all__daha sonra açıklanan değişkeni ayarlayabilir .

...

Tek çözüm, paket yazarının paketin açık bir dizinini sağlamasıdır. İmport deyimi aşağıdaki kuralı kullanır: bir paketin __init__.pykodu adlı bir liste tanımlarsa __all__, paket içe aktarma * ile karşılaşıldığında alınması gereken modül adlarının listesi olarak alınır. Paketin yeni bir sürümü çıktığında bu listeyi güncel tutmak paket yazarına bağlıdır. Paket yazarları, paketlerinden * içe aktarma için bir kullanım görmüyorlarsa desteklememeye de karar verebilirler. Örneğin, dosya sounds/effects/__init__.pyaşağıdaki kodu içerebilir:

__all__ = ["echo", "surround", "reverse"]

Bu from sound.effects import *, ses paketinin adlandırılmış üç alt modülünü içe aktaracağı anlamına gelir .


3

Bu şimdiye kadar bulduğum en iyi yol:

from os.path import dirname, join, isdir, abspath, basename
from glob import glob
pwd = dirname(__file__)
for x in glob(join(pwd, '*.py')):
    if not x.startswith('__'):
        __import__(basename(x)[:-3], globals(), locals())

2

Eklemeniz importlibgereken tek şey şu:

from importlib import import_module
from pathlib import Path

__all__ = [
    import_module(f".{f.stem}", __package__)
    for f in Path(__file__).parent.glob("*.py")
    if "__" not in f.stem
]
del import_module, Path

Meşru mypy sorununa Bu potansiyel müşteriler: error: Type of __all__ must be "Sequence[str]", not "List[Module]". __all__Bu import_moduletemelli yaklaşım kullanılıyorsa tanımlama gerekli değildir .
Acumenus

1

Standart kütüphaneden pkgutil modülüne bakın. Dizinde bir __init__.pydosya olduğu sürece tam olarak istediğinizi yapmanıza izin verecektir . __init__.pyDosya boş olabilir.


1

Bunun için bir modül oluşturdum, bu da __init__.py(veya başka bir yardımcı dosyaya) güvenmiyor ve sadece aşağıdaki iki satırı yazmamı sağlıyor:

import importdir
importdir.do("Foo", globals())

Tekrar kullanmaktan veya katkıda bulunmaktan çekinmeyin: http://gitlab.com/aurelien-lourot/importdir


0

Sadece importlib ile içe aktarın ve paketteki geri çekilişe__all__ ( addeylem isteğe bağlıdır) ekleyin __init__.py.

/Foo
    bar.py
    spam.py
    eggs.py
    __init__.py

# __init__.py
import os
import importlib
pyfile_extes = ['py', ]
__all__ = [importlib.import_module('.%s' % filename, __package__) for filename in [os.path.splitext(i)[0] for i in os.listdir(os.path.dirname(__file__)) if os.path.splitext(i)[1] in pyfile_extes] if not filename.startswith('__')]
del os, importlib, pyfile_extes

Pyfile_extes nerede tanımlanır?
Jeppe

özlediğim için üzgünüm, düzeltildi. Bu genellikle almak istediğiniz python dosyasının uzantısıdırpy
Cheney

0

Ne zaman from . import *yeterince iyi değil, bu üzerinde bir gelişmedir ted tarafından cevap . Özellikle, __all__bu yaklaşımda kullanımı gerekli değildir.

"""Import all modules that exist in the current directory."""
# Ref https://stackoverflow.com/a/60861023/
from importlib import import_module
from pathlib import Path

for f in Path(__file__).parent.glob("*.py"):
    module_name = f.stem
    if (not module_name.startswith("_")) and (module_name not in globals()):
        import_module(f".{module_name}", __package__)
    del f, module_name
del import_module, Path

module_name not in globals()Döngüsel içe aktarma riskini taşıyabileceğinden, zaten içe aktarılmışsa modülün yeniden içe aktarılmasını önleme amaçlı olduğunu unutmayın .

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.