Bagaimana cara memuat semua modul dalam folder?

269

Bisakah seseorang memberi saya cara yang baik untuk mengimpor seluruh direktori modul?
Saya memiliki struktur seperti ini:

/Foo
    bar.py
    spam.py
    eggs.py

Saya mencoba mengubahnya menjadi paket dengan menambahkan __init__.pydan melakukan from Foo import *tetapi tidak berhasil seperti yang saya harapkan.

Evan Fosmark
sumber
2
Cara init .py cukup benar. Bisakah Anda memposting kode yang tidak berfungsi?
balpha
9
Bisakah Anda mendefinisikan "tidak berhasil"? Apa yang terjadi? Apa pesan kesalahan yang Anda dapatkan?
S.Lott
Apakah ini Pythonic atau direkomendasikan? "Eksplisit lebih baik daripada implisit."
yangmillstheory
Eksplisit memang lebih baik. Tetapi apakah Anda benar-benar ingin mengatasi pesan 'tidak ditemukan' yang menjengkelkan ini setiap kali Anda menambahkan modul baru. Saya pikir jika Anda memiliki direktori paket yang berisi banyak fungsi-modul kecil, maka ini adalah cara terbaik untuk melakukannya. Asumsi saya adalah: 1. modul agak sederhana 2. Anda menggunakan paket untuk kerapian kode
Ido_f

Jawaban:

400

Daftar semua .pyfile python ( ) di folder saat ini dan masukkan sebagai __all__variabel__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')]
Anurag Uniyal
sumber
57
Pada dasarnya saya bisa memasukkan file python ke direktori tanpa konfigurasi lebih lanjut dan membuatnya dijalankan oleh skrip yang berjalan di tempat lain.
Evan Fosmark
5
@NiallDouglas jawaban ini untuk pertanyaan spesifik yang ditanyakan OP, ia tidak memiliki file zip dan file pyc dapat dimasukkan dengan mudah, dan Anda juga lupa .pyd atau .so libs dll juga
Anurag Uniyal
35
Satu-satunya hal yang akan saya tambahkan adalah if not os.path.basename(f).startswith('_')atau paling tidak if not f.endswith('__init__.py')pada akhir pemahaman daftar
Pykler
10
Untuk membuatnya lebih kuat, pastikan os.path.isfile(f)juga True. Itu akan menyaring symlink dan direktori yang rusak seperti somedir.py/(kasus sudut, saya akui, tapi masih ...)
MestreLion
12
Menambahkan from . import *setelah pengaturan __all__jika Anda ingin submodul akan tersedia menggunakan .(misalnya sebagai module.submodule1, module.submodule2, dll).
ostrokach
133

Tambahkan __all__Variabel ke yang __init__.pyberisi:

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

Lihat juga http://docs.python.org/tutorial/modules.html

Stefan
sumber
163
Ya, ya, tetapi apakah ada cara untuk menjadikannya dinamis?
Evan Fosmark
27
Kombinasi os.listdir(), beberapa pemfilteran, stripping .pyekstensi dan __all__.
Tidak berfungsi untuk saya sebagai kode sampel di sini github.com/namgivu/python-import-all/blob/master/error_app.py . Mungkin saya melewatkan sesuatu di sana?
Nam G VU
1
Saya menemukan diri saya - untuk menggunakan variabel / objek yang didefinisikan dalam modul-modul itu, kita harus menggunakan jalur referensi lengkap misalnya moduleName.varNameref. stackoverflow.com/a/710603/248616
Nam G VU
1
@NamGVU: Kode ini dalam jawaban saya untuk pertanyaan terkait akan mengimpor semua nama sub-modul publik ke dalam namespace paket.
Martineau
54

Pembaruan pada 2017: Anda mungkin ingin menggunakan importlib .

Jadikan direktori Foo paket dengan menambahkan __init__.py. Dalam __init__.pyadd itu:

import bar
import eggs
import spam

Karena Anda menginginkannya dinamis (yang mungkin atau mungkin bukan ide yang bagus), daftarkan semua file py dengan dir direktori dan impor dengan sesuatu seperti ini:

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

Lalu, dari kode Anda, lakukan ini:

import Foo

Anda sekarang dapat mengakses modul dengan

Foo.bar
Foo.eggs
Foo.spam

dll. from Foo import *bukan ide yang bagus karena beberapa alasan, termasuk bentrokan nama dan membuatnya sulit untuk menganalisis kode.

Lennart Regebro
sumber
1
Tidak buruk, tetapi jangan lupa bahwa Anda juga dapat mengimpor file .pyc dan .pyo.
Evan Fosmark
7
tbh, saya menemukan __import__hackish, saya pikir akan lebih baik untuk menambahkan nama __all__dan kemudian meletakkannya from . import *di bagian bawah script
freeforall tousez
1
Saya pikir ini lebih baik daripada versi glob.
lpapp
8
__import__bukan untuk penggunaan umum, itu digunakan oleh interpreter, digunakan importlib.import_module()sebagai gantinya.
Andrew_1510
2
Contoh pertama sangat membantu, terima kasih! Di bawah Python 3.6.4 saya harus melakukan from . import eggsdll di __init__.pysebelum Python dapat mengimpor. Dengan hanya import eggssaya dapatkan ModuleNotFoundError: No module named 'eggs'ketika mencoba import Foodi main.pydalam direktori di atas.
Nick
41

Memperluas jawaban Mihail, saya percaya cara non-hackish (seperti dalam, tidak menangani jalur file secara langsung) adalah sebagai berikut:

  1. buat __init__.pyfile kosong di bawahFoo/
  2. Menjalankan
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')

Anda akan mendapatkan:

<module 'Foo.bar' from '/home/.../Foo/bar.pyc'>
<module 'Foo.spam' from '/home/.../Foo/spam.pyc'>
Luca Invernizzi
sumber
Ini adalah sebagian besar jalan menuju jawaban yang benar - ini menangani arsip ZIP, tetapi tidak menulis init atau mengimpor. Lihat automodinit di bawah ini.
Niall Douglas
1
Hal lain: contoh di atas tidak memeriksa sys.modules untuk melihat apakah modul sudah dimuat. Tanpa itu periksa di atas akan memuat modul kedua kalinya :)
Niall Douglas
3
Ketika saya menjalankan load_all_modules_from_dir ('Foo / bar') dengan kode Anda, saya mendapatkan "RuntimeWarning: Modul induk 'Foo / bar' tidak ditemukan saat menangani impor absolut" - untuk menekan ini, saya harus mengatur full_package_name = '. Join. dirname.split (os.path.sep) + package_name]) dan juga mengimpor Foo.bar
Alex Dupuy
Mereka RuntimeWarningpesan juga dapat dihindari dengan tidak menggunakan full_package_name sama sekali: importer.find_module(package_name).load_module(package_name).
Artfunkel
The RuntimeWarningkesalahan juga dapat dihindari (dengan cara yang bisa dibilang jelek) dengan mengimpor induk (AKA dirname). Salah satu cara untuk melakukannya adalah - if dirname not in sys.modules: pkgutil.find_loader(dirname).load_module(dirname). Tentu saja, itu hanya berfungsi jika dirnamejalur relatif satu komponen; tidak ada garis miring. Secara pribadi, saya lebih suka pendekatan @ Artfunkel menggunakan package_name dasar sebagai gantinya.
dannysauer
32

Python, sertakan semua file di bawah direktori:

Untuk pemula yang tidak bisa bekerja yang membutuhkan tangan mereka.

  1. Buat folder / home / el / foo dan buat file di main.pybawah / home / el / foo Masukkan kode ini di sana:

    from hellokitty import *
    spam.spamfunc()
    ham.hamfunc()
  2. Buat direktori /home/el/foo/hellokitty

  3. Buat file di __init__.pybawah /home/el/foo/hellokittydan masukkan kode ini di sana:

    __all__ = ["spam", "ham"]
  4. Buat dua file python: spam.pydan di ham.pybawah/home/el/foo/hellokitty

  5. Tetapkan fungsi di dalam spam.py:

    def spamfunc():
      print("Spammity spam")
  6. Tentukan fungsi di dalam ham.py:

    def hamfunc():
      print("Upgrade from baloney")
  7. Menjalankannya:

    el@apollo:/home/el/foo$ python main.py 
    spammity spam
    Upgrade from baloney
Eric Leschinski
sumber
1
Tidak hanya untuk pemula, tetapi juga para pengembang Python berpengalaman yang menyukai jawaban yang jelas. Terima kasih.
Michael Scheper
Tetapi menggunakan import *dianggap buruk praktek pengkodean Python. Bagaimana Anda melakukan ini tanpa itu?
Rachael Blake
16

Saya bosan dengan masalah ini sendiri, jadi saya menulis sebuah paket bernama automodinit untuk memperbaikinya. Anda bisa mendapatkannya dari http://pypi.python.org/pypi/automodinit/ .

Penggunaannya seperti ini:

  1. Sertakan automodinitpaket ke dalamsetup.py dependensi .
  2. Ganti semua file __init__.py seperti ini:
__all__ = ["Saya akan ditulis ulang"]
# Jangan memodifikasi baris di atas, atau baris ini!
impor automodinit
automodinit.automodinit (__ name__, __file__, global ())
del automodinit
# Apa pun yang Anda inginkan dapat pergi setelah di sini, itu tidak akan dimodifikasi.

Itu dia! Mulai sekarang mengimpor modul akan menetapkan __all__ ke daftar file .py [co] dalam modul dan juga akan mengimpor masing-masing file tersebut seolah-olah Anda telah mengetik:

for x in __all__: import x

Karenanya efek "dari impor M *" sama persis dengan "impor M".

automodinit senang berlari dari dalam arsip ZIP dan karenanya ZIP aman.

Niall

Niall Douglas
sumber
pip tidak dapat mengunduh automodinit karena tidak ada yang diunggah di pypi untuk itu.
kanzure
Terima kasih atas laporan bug di github. Saya sudah memperbaiki ini di v0.13. Niall
Niall Douglas
11

Saya tahu saya memperbarui posting yang cukup lama, dan saya mencoba menggunakan automodinit , tetapi ternyata proses setupnya rusak untuk python3. Jadi, berdasarkan jawaban Luca, saya memberikan jawaban yang lebih sederhana - yang mungkin tidak bekerja dengan .zip - untuk masalah ini, jadi saya pikir saya harus membagikannya di sini:

dalam __init__.pymodul dari yourpackage:

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

dan dalam paket lain di bawah ini yourpackage:

from yourpackage import *

Maka Anda akan memiliki semua modul yang ditempatkan di dalam paket dimuat, dan jika Anda menulis modul baru, itu akan diimpor secara otomatis juga. Tentu saja, gunakan hal-hal semacam itu dengan hati-hati, dengan kekuatan besar datang tanggung jawab besar.

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

Saya juga mengalami masalah ini dan ini adalah solusi saya:

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

Fungsi ini membuat file (dalam folder yang disediakan) bernama __init__.py, yang berisi __all__variabel yang menampung setiap modul di folder.

Misalnya, saya memiliki folder bernama Test yang berisi:

Foo.py
Bar.py

Jadi dalam skrip saya ingin modul diimpor ke saya akan menulis:

loadImports('Test/')
from Test import *

Ini akan mengimpor semuanya dari Testdan __init__.pyfile dalam Testsekarang akan berisi:

__all__ = ['Foo','Bar']
Penguinblade
sumber
sempurna, persis apa yang saya cari
uma mahesh
4

Contoh Anurag dengan beberapa koreksi:

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")]
kzar
sumber
4

Anurag Uniyal menjawab dengan perbaikan yang disarankan!

#!/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  
Eduardo Lucio
sumber
3

Lihat itu __init__.pydefinisi Anda __all__. The modul - paket doc kata

The __init__.pyfile yang diperlukan untuk membuat Python memperlakukan direktori sebagai mengandung paket; ini dilakukan untuk mencegah direktori dengan nama umum, seperti string, dari menyembunyikan modul valid yang terjadi kemudian pada jalur pencarian modul. Dalam kasus yang paling sederhana, __init__.pybisa berupa file kosong, tetapi juga dapat menjalankan kode inisialisasi untuk paket atau mengatur __all__variabel, yang dijelaskan nanti.

...

Satu-satunya solusi adalah bagi pembuat paket untuk memberikan indeks paket secara eksplisit. Pernyataan impor menggunakan konvensi berikut: jika __init__.pykode paket mendefinisikan daftar bernama __all__, itu diambil menjadi daftar nama modul yang harus diimpor ketika dari paket impor * ditemukan. Terserah pembuat paket untuk tetap memperbarui daftar ini ketika versi baru dari paket dirilis. Penulis paket juga dapat memutuskan untuk tidak mendukungnya, jika mereka tidak melihat penggunaan untuk mengimpor * dari paket mereka. Misalnya, file tersebut sounds/effects/__init__.pydapat berisi kode berikut:

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

Ini berarti bahwa from sound.effects import *akan mengimpor tiga submodul bernama paket suara.

Gimel
sumber
3

Ini adalah cara terbaik yang saya temukan sejauh ini:

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())
Farsheed
sumber
2

Menggunakan importlibsatu-satunya hal yang harus Anda tambahkan adalah

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
ted
sumber
Ini mengarah ke masalah mypy yang sah: error: Type of __all__ must be "Sequence[str]", not "List[Module]". Mendefinisikan __all__tidak diperlukan jika import_modulependekatan berbasis ini digunakan.
Acumenus
1

Lihatlah modul pkgutil dari perpustakaan standar. Ini akan membiarkan Anda melakukan apa yang Anda inginkan selama Anda memiliki __init__.pyfile di direktori. The __init__.pyfile dapat kosong.

Mihail Mihaylov
sumber
1

Saya telah membuat modul untuk itu, yang tidak bergantung pada __init__.py(atau file tambahan lainnya) dan membuat saya mengetik hanya dua baris berikut:

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

Jangan ragu untuk menggunakan kembali atau berkontribusi: http://gitlab.com/aurelien-lourot/importdir

Aurelien
sumber
0

Cukup impor mereka dengan importlib dan tambahkan ke __all__( addtindakan adalah opsional) secara berulang di __init__.pypaket.

/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
Cheney
sumber
Di mana didefinisikan pyfile_extes?
Jeppe
maaf karena melewatkannya, sekarang sudah diperbaiki. Ini adalah ekstensi dari file python yang ingin Anda impor, biasanya hanyapy
Cheney
0

Ketika from . import *tidak cukup baik, ini merupakan peningkatan atas jawaban oleh ted . Secara khusus, penggunaan __all__tidak diperlukan dengan pendekatan ini.

"""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

Catatan yang module_name not in globals()dimaksudkan untuk menghindari pengimporan kembali modul jika sudah diimpor, karena ini dapat berisiko impor siklik.

Acumenus
sumber