Làm thế nào để tải tất cả các mô-đun trong một thư mục?


269

Ai đó có thể cung cấp cho tôi một cách tốt để nhập toàn bộ thư mục mô-đun không?
Tôi có một cấu trúc như thế này:

/Foo
    bar.py
    spam.py
    eggs.py

Tôi đã cố gắng chuyển đổi nó thành một gói bằng cách thêm __init__.pyvà thực hiện from Foo import *nhưng nó không hoạt động như tôi mong đợi.


2
Cách init .py là khá chính xác. Bạn có thể gửi mã không hoạt động?
balpha

9
Bạn có thể định nghĩa "không làm việc"? Chuyện gì đã xảy ra? Bạn đã nhận được thông báo lỗi gì?
S.Lott

Đây là Pythonic hay được đề nghị? "Rõ ràng là tốt hơn so với ngầm."
yangmillstheory

Rõ ràng là thực sự tốt hơn. Nhưng bạn có thực sự muốn giải quyết những thông báo 'không tìm thấy' gây phiền nhiễu này mỗi khi bạn thêm một mô-đun mới. Tôi nghĩ rằng nếu bạn có một thư mục gói chứa nhiều hàm mô-đun nhỏ, thì đây là cách tốt nhất để thực hiện. Giả định của tôi là: 1. mô-đun khá đơn giản 2. bạn sử dụng gói cho sự gọn gàng của mã
Ido_f

Câu trả lời:


400

Liệt kê tất cả .pycác tệp python ( ) trong thư mục hiện tại và đặt chúng dưới dạng __all__biến trong__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
Về cơ bản để tôi có thể thả các tệp python vào một thư mục không có cấu hình nào nữa và chúng sẽ được thực thi bởi một tập lệnh chạy ở một nơi khác.
Evan Fosmark

5
@NiallDoureb câu trả lời này dành cho một câu hỏi cụ thể mà OP đã hỏi, anh ta không có tệp zip và các tệp pyc có thể được đưa vào một cách dễ dàng và bạn đang quên .pyd hoặc .so libs, v.v.
Anurag Uniyal

35
Điều duy nhất tôi muốn thêm là if not os.path.basename(f).startswith('_')hoặc ít nhất là if not f.endswith('__init__.py')đến cuối phần hiểu danh sách
Pykler

10
Để làm cho nó mạnh mẽ hơn, cũng chắc chắn os.path.isfile(f)là có True. Điều đó sẽ lọc ra các liên kết và thư mục bị hỏng như somedir.py/(trường hợp góc, tôi thừa nhận, nhưng vẫn ...)
MestreLion

12
Thêm from . import *sau khi thiết lập __all__nếu bạn muốn submodules có sẵn sử dụng .(ví dụ như module.submodule1, module.submodule2, vv).
Ostrokach

133

Thêm __all__biến để __init__.pychứa:

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

Xem thêm http://docs.python.org/tutorial/modules.html


163
Vâng, vâng, nhưng có cách nào để nó năng động không?
Evan Fosmark

27
Kết hợp os.listdir(), một số bộ lọc, tước bỏ .pyphần mở rộng và __all__.

Không hoạt động đối với tôi như mã mẫu ở đây github.com/namgivu/python-import-all/blob/master/error_app.py . Có lẽ tôi bỏ lỡ một cái gì đó ở đó?
Nam G VU

1
Tôi tự tìm ra - để sử dụng biến / đối tượng được xác định trong các mô-đun đó, chúng tôi phải sử dụng đường dẫn tham chiếu đầy đủ, ví dụ: moduleName.varNameref. stackoverflow.com/a/710603/248616
Nam G VU

1
@NamGVU: Mã này trong câu trả lời của tôi cho một câu hỏi liên quan sẽ nhập tất cả tên của các mô-đun con công khai vào không gian tên của gói.
martineau

54

Cập nhật năm 2017: có lẽ bạn muốn sử dụng importlibthay thế.

Làm cho thư mục Foo một gói bằng cách thêm một __init__.py. Trong đó __init__.pythêm:

import bar
import eggs
import spam

Vì bạn muốn nó động (có thể là một ý tưởng hay), hãy liệt kê tất cả các tệp py với danh sách dir và nhập chúng với nội dung như sau:

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

Sau đó, từ mã của bạn làm điều này:

import Foo

Bây giờ bạn có thể truy cập các mô-đun với

Foo.bar
Foo.eggs
Foo.spam

vv from Foo import *không phải là một ý tưởng tốt vì một số lý do, bao gồm xung đột tên và làm cho nó khó để phân tích mã.


1
Không tệ, nhưng đừng quên rằng bạn cũng có thể nhập các tệp .pyc và .pyo.
Evan Fosmark

7
tbh, tôi tìm thấy __import__hackish, tôi nghĩ sẽ tốt hơn nếu thêm tên vào __all__và sau đó đặt from . import *ở dưới cùng của kịch bản
freeforall tousez

1
Tôi nghĩ rằng điều này là đẹp hơn so với phiên bản toàn cầu.
lpapp

8
__import__không dành cho sử dụng chung, nó được sử dụng bởi interpreter, sử dụng importlib.import_module()thay thế.
Andrew_1510

2
Ví dụ đầu tiên rất hữu ích, cảm ơn! Trong Python 3.6.4 tôi đã phải làm from . import eggsv.v. __init__.pytrước khi Python có thể nhập. Với chỉ import eggstôi nhận được ModuleNotFoundError: No module named 'eggs'khi cố gắng import Footrong main.pytrong thư mục trên.
Nick

41

Mở rộng câu trả lời của Mihail, tôi tin rằng cách không hack (như trong, không xử lý trực tiếp đường dẫn tệp) là như sau:

  1. tạo một __init__.pytập tin trống dướiFoo/
  2. Hành hình
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')

Bạn sẽ nhận được:

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

Đây là phần lớn cách để có một câu trả lời đúng - nó xử lý các lưu trữ ZIP, nhưng không viết init cũng không nhập. Xem automodinit dưới đây.
Niall Douglas

1
Một điều nữa: ví dụ trên không kiểm tra sys.modules để xem mô-đun đã được tải chưa. Nếu không có kiểm tra ở trên sẽ tải mô-đun lần thứ hai :)
Niall Douglas

3
Khi tôi chạy load_all_modules_from_dir ('Foo / bar') với mã của bạn, tôi nhận được "RuntimeWarning: Mô-đun mẹ 'Foo / bar' không tìm thấy trong khi xử lý nhập tuyệt đối" - để chặn điều này, tôi phải đặt full_package_name = '.' dirname.split (os.path.sep) + pack_name]) và cũng nhập Foo.bar
Alex Dupuy

Những RuntimeWarningtin nhắn đó cũng có thể tránh được bằng cách không sử dụng full_package_name : importer.find_module(package_name).load_module(package_name).
Artfunkel 4/11/2016

Các RuntimeWarninglỗi cũng có thể tránh được (theo cách xấu có thể tranh cãi) bằng cách nhập cha mẹ (tên thư mục AKA). Một cách để làm điều đó là - if dirname not in sys.modules: pkgutil.find_loader(dirname).load_module(dirname). Tất nhiên, điều đó chỉ hoạt động nếu dirnamelà một đường dẫn tương đối đơn thành phần; không chém. Cá nhân, tôi thích cách tiếp cận của @ Artfunkel hơn là sử dụng gói tên_bơ cơ sở thay thế.
dannysauer

31

Python, bao gồm tất cả các tệp trong một thư mục:

Đối với những người mới, những người không thể làm cho nó hoạt động, những người cần nắm tay họ.

  1. Tạo một thư mục / home / el / foo và tạo một tập tin main.pydưới / home / el / foo Đặt mã này vào đó:

    from hellokitty import *
    spam.spamfunc()
    ham.hamfunc()
  2. Tạo một thư mục /home/el/foo/hellokitty

  3. Tạo một tập tin __init__.pybên dưới /home/el/foo/hellokittyvà đặt mã này vào đó:

    __all__ = ["spam", "ham"]
  4. Tạo hai tệp python: spam.pyham.pydưới/home/el/foo/hellokitty

  5. Xác định một chức năng bên trong spam.py:

    def spamfunc():
      print("Spammity spam")
  6. Xác định một chức năng bên trong ham.py:

    def hamfunc():
      print("Upgrade from baloney")
  7. Chạy nó:

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

1
Không chỉ dành cho người mới, mà cả những nhà phát triển Python có kinh nghiệm, những người thích câu trả lời rõ ràng. Cảm ơn.
Michael Scheper

Nhưng sử dụng import *được coi là thực hành mã hóa Python xấu. Làm thế nào để bạn làm điều này mà không có điều đó?
Rachael Blake

16

Tôi cảm thấy mệt mỏi với vấn đề này, vì vậy tôi đã viết một gói có tên là automodinit để khắc phục nó. Bạn có thể lấy nó từ http://pypi.python.org/pypi/automodinit/ .

Cách sử dụng là như thế này:

  1. Bao gồm các automodinitgói vào setup.pyphụ thuộc của bạn .
  2. Thay thế tất cả các tệp __init__.py như thế này:
__all__ = ["Tôi sẽ được viết lại"]
# Đừng sửa đổi dòng trên hoặc dòng này!
nhập khẩu tự động
automodinit.automodinit (__ name__, __file__, globalals ())
del automodinit
# Bất cứ điều gì khác bạn muốn có thể đi sau đây, nó sẽ không được sửa đổi.

Đó là nó! Từ giờ, việc nhập mô-đun sẽ đặt __all__ thành danh sách các tệp .py [co] trong mô-đun và cũng sẽ nhập từng tệp đó như thể bạn đã nhập:

for x in __all__: import x

Do đó, hiệu ứng của "từ M nhập *" khớp chính xác với "nhập M".

automodinit rất vui khi chạy từ bên trong kho lưu trữ ZIP và do đó ZIP an toàn.

Không


Pip không thể tải xuống automodinit vì không có gì được tải lên trên pypi cho nó.
kanzure

Cảm ơn báo cáo lỗi trên github. Tôi đã sửa lỗi này trong v0.13. Niall
Niall Douglas

11

Tôi biết tôi đang cập nhật một bài viết khá cũ và tôi đã thử sử dụng automodinit , nhưng phát hiện ra quá trình thiết lập của nó bị hỏng đối với python3. Vì vậy, dựa trên câu trả lời của Luca, tôi đã đưa ra một câu trả lời đơn giản hơn - có thể không hoạt động với .zip - cho vấn đề này, vì vậy tôi nghĩ rằng tôi nên chia sẻ nó ở đây:

trong __init__.pymô-đun từ yourpackage:

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

và trong một gói khác dưới đây yourpackage:

from yourpackage import *

Sau đó, bạn sẽ có tất cả các mô-đun được đặt trong gói được tải và nếu bạn viết một mô-đun mới, nó cũng sẽ được nhập tự động. Tất nhiên, sử dụng những thứ đó một cách cẩn thận, với sức mạnh lớn đi kèm với trách nhiệm lớn.


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

5

Tôi cũng đã gặp phải vấn đề này và đây là giải pháp của tôi:

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

Hàm này tạo một tệp (trong thư mục được cung cấp) có tên __init__.py, chứa một __all__biến chứa mọi mô-đun trong thư mục.

Ví dụ: tôi có một thư mục có tên Test :

Foo.py
Bar.py

Vì vậy, trong kịch bản tôi muốn các mô-đun được nhập vào tôi sẽ viết:

loadImports('Test/')
from Test import *

Điều này sẽ nhập mọi thứ từ Test__init__.pytệp trong đó Testsẽ chứa:

__all__ = ['Foo','Bar']

hoàn hảo, chính xác những gì tôi đang tìm kiếm
uma mahesh

4

Ví dụ của Anurag với một vài sửa chữa:

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

Anurag Uniyal trả lời với những cải tiến được đề xuất!

#!/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

Xem __init__.pyđịnh nghĩa của bạn __all__. Các mô-đun - gói doc nói

Các __init__.pytệp được yêu cầu để làm cho Python coi các thư mục là chứa các gói; điều này được thực hiện để ngăn các thư mục có tên chung, chẳng hạn như chuỗi, vô tình ẩn các mô-đun hợp lệ xảy ra sau này trên đường dẫn tìm kiếm mô-đun. Trong trường hợp đơn giản nhất, __init__.pycó thể chỉ là một tệp trống, nhưng nó cũng có thể thực thi mã khởi tạo cho gói hoặc đặt __all__biến, được mô tả sau.

...

Giải pháp duy nhất là để tác giả gói cung cấp một chỉ mục rõ ràng của gói. Câu lệnh nhập sử dụng quy ước sau: nếu __init__.pymã của gói xác định danh sách có tên __all__, thì nó được coi là danh sách tên mô-đun nên được nhập khi gặp phải nhập gói *. Tùy thuộc vào tác giả gói để giữ cho danh sách này được cập nhật khi một phiên bản mới của gói được phát hành. Các tác giả gói cũng có thể quyết định không hỗ trợ nó, nếu họ không thấy việc sử dụng để nhập * từ gói của họ. Ví dụ: tệp sounds/effects/__init__.pycó thể chứa mã sau:

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

Điều này có nghĩa là from sound.effects import *sẽ nhập ba mô hình con có tên của gói âm thanh.


3

Đây là cách tốt nhất mà tôi đã tìm thấy cho đến nay:

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

Sử dụng importlibthứ duy nhất bạn phải thêm là

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

Điều này dẫn đến một vấn đề mypy hợp pháp : error: Type of __all__ must be "Sequence[str]", not "List[Module]". Xác định __all__là không cần thiết nếu import_modulephương pháp dựa trên này được sử dụng.
Acumenus

1

Nhìn vào mô-đun pkgutil từ thư viện tiêu chuẩn. Nó sẽ cho phép bạn làm chính xác những gì bạn muốn miễn là bạn có một __init__.pytập tin trong thư mục. Các __init__.pytập tin có thể để trống.


1

Tôi đã tạo một mô-đun cho điều đó, không dựa vào __init__.py(hoặc bất kỳ tệp phụ trợ nào khác) và khiến tôi chỉ gõ hai dòng sau:

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

Vui lòng sử dụng lại hoặc đóng góp: http://gitlab.com/aurelien-lourot/importdir


0

Chỉ cần nhập chúng bằng cách importlib và thêm chúng vào __all__( addhành động này là không bắt buộc) trong recurse trong __init__.pycác gói.

/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 được định nghĩa ở đâu?
Jeppe

xin lỗi vì đã bỏ lỡ nó Đây là phần mở rộng của tệp python mà bạn muốn nhập, thường chỉ làpy
Cheney

0

Khi from . import *không đủ tốt, đây là một cải tiến so với câu trả lời của ted . Cụ thể, việc sử dụng __all__là không cần thiết với phương pháp này.

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

Lưu ý rằng module_name not in globals()nhằm tránh nhập lại mô-đun nếu nó đã được nhập, vì điều này có thể gây rủi ro cho việc nhập theo chu kỳ.

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.