Как загрузить все модули в папке?


269

Может ли кто-нибудь предоставить мне хороший способ импорта целого каталога модулей?
У меня есть такая структура:

/Foo
    bar.py
    spam.py
    eggs.py

Я попытался просто преобразовать его в пакет, добавив __init__.pyи сделав, from Foo import *но это не сработало так, как я надеялся.


2
Способ init .py довольно правильный. Можете ли вы опубликовать код, который не работал?
— Балфа

9
Можете ли вы определить «не работает»? Что произошло? Какое сообщение об ошибке вы получили?
— С.Лотт

Это Pythonic или рекомендуется? «Явное лучше, чем неявное».
— yangmillstheory

Явное действительно лучше. Но вы действительно хотели бы обращаться к этим назойливым «не найденным» сообщениям каждый раз, когда вы добавляете новый модуль. Я думаю, что если у вас есть каталог пакетов, содержащий много небольших функций-модулей, то это самый хороший способ сделать это. Мои предположения: 1. модуль довольно прост 2. вы используете пакет для аккуратности кода
— Ido_f

Ответы:


400

Перечислите все .pyфайлы python ( ) в текущей папке и поместите их как __all__переменные в__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
По сути, я могу поместить файлы python в каталог без дальнейшей настройки, чтобы они выполнялись скриптом, выполняющимся где-то еще.
— Эван Фосмарк

5
@NiallDouglas этот ответ для конкретного вопроса, который задал OP, у него не было zip-файла, и pyc-файлы можно было легко включить, и вы тоже забыли .pyd или .so libs и т. Д.
— Anurag Uniyal

35
Единственное, что я хотел бы добавить, if not os.path.basename(f).startswith('_')или, по крайней мере, if not f.endswith('__init__.py')до конца понимания списка
— Пиклер

10
Чтобы сделать его более надежным, также убедитесь, что os.path.isfile(f)есть True. Это отфильтровывало бы битые символические ссылки и каталоги, как somedir.py/(угловой случай, я признаю, но все же ...)
— MestreLion

12
Добавьте from . import *после настройки, __all__если вы хотите, чтобы субмодули были доступны с помощью .(например, как module.submodule1, module.submodule2и т. Д.).
— Острокач

133

Добавьте __all__переменную к __init__.pyсодержанию:

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

Смотрите также http://docs.python.org/tutorial/modules.html


163
Да, да, но есть ли способ сделать это динамичным?
— Эван Фосмарк

27
Комбинация os.listdir(), некоторая фильтрация, удаление .pyрасширения и др __all__.

Не работает для меня в качестве примера кода здесь github.com/namgivu/python-import-all/blob/master/error_app.py . Может я что-то там пропускаю?
— Нам Г ВУ

1
Я выяснил сам - чтобы использовать переменную / объект, определенный в этих модулях, мы должны использовать полный путь ссылки, например, moduleName.varNameref. stackoverflow.com/a/710603/248616
— Nam G VU

1
@NamGVU: Этот код в моем ответе на связанный вопрос импортирует имена всех общедоступных субмодулей в пространство имен пакета.
— Мартино

54

Обновление в 2017 году: вы, вероятно, хотите использовать importlibвместо этого.

Сделайте каталог Foo пакетом, добавив __init__.py. В это __init__.pyдобавить:

import bar
import eggs
import spam

Поскольку вы хотите, чтобы он был динамическим (что может или не может быть хорошей идеей), перечислите все py-файлы с помощью list dir и импортируйте их примерно так:

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

Затем из вашего кода сделайте это:

import Foo

Теперь вы можете получить доступ к модулям с

Foo.bar
Foo.eggs
Foo.spam

и т. д. from Foo import *не очень хорошая идея по нескольким причинам, включая конфликт имен и усложнение анализа кода.


1
Неплохо, но не забывайте, что вы также можете импортировать файлы .pyc и .pyo.
— Эван Фосмарк

7
ТБХ, я нахожу __import__хакером, я думаю, что было бы лучше добавить имена, __all__а затем поместить from . import *в
— конец

1
Я думаю, что это лучше, чем глобальная версия.
— lpapp

8
__import__не для общего использования, он используется interpreter, используйте importlib.import_module()вместо этого.
— Andrew_1510

2
Первый пример был очень полезным, спасибо! В Python 3.6.4 я должен был сделать from . import eggsи т. Д. __init__.pyДо того, как Python мог импортировать. Только import eggsя получаю ModuleNotFoundError: No module named 'eggs'при попытке import Fooв main.pyв каталоге выше.
— Ник

41

Продолжая ответ Михаила, я считаю, что нехакерский способ (например, не обрабатывать пути к файлам напрямую) заключается в следующем:

  1. создать пустой __init__.pyфайл подFoo/
  2. казнить
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')

Ты получишь:

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

В большинстве случаев это правильный ответ - он обрабатывает ZIP-архивы, но не записывает ни init, ни import. Смотрите автодинит ниже.
— Найл Дуглас

1
Другое дело: приведенный выше пример не проверяет sys.modules, чтобы увидеть, загружен ли модуль. Без этой проверки вышесказанное загрузит модуль второй раз :)
— Найл Дуглас

3
Когда я запускаю load_all_modules_from_dir ('Foo / bar') с вашим кодом, я получаю "RuntimeWarning: родительский модуль 'Foo / bar' не найден при обработке абсолютного импорта" - чтобы подавить это, я должен установить full_package_name = '.'. Join ( dirname.split (os.path.sep) + package_name]), а также импортируйте Foo.bar
— Алекс

Эти RuntimeWarningсообщения также можно избежать, если использовать full_package_name вообще: importer.find_module(package_name).load_module(package_name).
— Артфункель

Эти RuntimeWarningошибки также можно избежать (возможно , в уродливом виде) путем импорта родителя (АКА DIRNAME). Один из способов сделать это - if dirname not in sys.modules: pkgutil.find_loader(dirname).load_module(dirname). Конечно, это работает, только если dirnameэто однокомпонентный относительный путь; без косых черт Лично я предпочитаю подход @ Artfunkel использовать вместо этого базовое имя_пакета.
— dannysauer

32

Python, включите все файлы в каталоге:

Для новичков, которые просто не могут заставить его работать, которым нужны руки.

  1. Создайте папку / home / el / foo и создайте файл main.pyв / home / el / foo. Добавьте туда этот код:

    from hellokitty import *
    spam.spamfunc()
    ham.hamfunc()
  2. Сделать каталог /home/el/foo/hellokitty

  3. Создайте файл __init__.pyв /home/el/foo/hellokittyи поместите этот код там:

    __all__ = ["spam", "ham"]
  4. Сделайте два файла Python: spam.pyи ham.pyпод/home/el/foo/hellokitty

  5. Определите функцию внутри spam.py:

    def spamfunc():
      print("Spammity spam")
  6. Определите функцию внутри ham.py:

    def hamfunc():
      print("Upgrade from baloney")
  7. Запустить его:

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

1
Не только для новичков, но и для опытных разработчиков Python, которым нравятся четкие ответы. Спасибо.
— Майкл Шепер

Но использование import *считается плохой практикой программирования на Python. Как ты это делаешь без этого?
— Рэйчел Блейк

16

Я устал от этой проблемы сам, поэтому я написал пакет под названием automodinit, чтобы исправить ее. Вы можете получить его по адресу http://pypi.python.org/pypi/automodinit/ .

Использование это так:

  1. Включите automodinitпакет в ваши setup.pyзависимости.
  2. Замените все файлы __init__.py следующим образом:
__all__ = ["Я буду переписан"]
# Не изменяйте строку выше или эту строку!
импорт автомодинит
automodinit.automodinit (__name__, __file__, globals ())
del automodinit
# Все остальное, что вы хотите, может пойти сюда, оно не будет изменено.

Это оно! Отныне импорт модуля установит __all__ в список .py [co] файлов в модуле, а также будет импортировать каждый из этих файлов, как если бы вы ввели:

for x in __all__: import x

Поэтому эффект «из M import *» в точности совпадает с «import M».

automodinit счастлив работать с внутренними архивами ZIP и поэтому безопасен для ZIP.

Найл


pip не может загрузить automodinit, потому что в pypi для него ничего не загружено.
— Канзуре

Спасибо за сообщение об ошибке на github. Я исправил это в v0.13. Найл
— Найл Дуглас

11

Я знаю, что обновляю довольно старый пост, и я пытался использовать automodinit, но обнаружил, что процесс установки нарушен для python3. Итак, основываясь на ответе Луки, я пришел к более простому ответу - который может не работать с .zip - на этот вопрос, поэтому я решил поделиться им здесь:

в __init__.pyмодуле из yourpackage:

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

и в другом пакете ниже yourpackage:

from yourpackage import *

Затем вы загрузите все модули, которые находятся в пакете, и если вы напишите новый модуль, он также будет автоматически импортирован. Конечно, используйте такие вещи с осторожностью, с большими полномочиями возникают большие обязанности.


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

5

Я также столкнулся с этой проблемой, и это было мое решение:

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

Эта функция создает файл (в предоставленной папке) с именем __init__.py, который содержит __all__переменную, которая содержит каждый модуль в папке.

Например, у меня есть папка с именем, Test которая содержит:

Foo.py
Bar.py

Поэтому в сценарии, в который я хочу импортировать модули, я напишу:

loadImports('Test/')
from Test import *

Это будет импортировать все из Testи __init__.pyфайл Testтеперь будет содержать:

__all__ = ['Foo','Bar']

идеально, именно то, что я ищу
— Ума Махеш

4

Пример Анурага с парой исправлений:

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 ответ с предложенными улучшениями!

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

Смотри, что __init__.pyопределяет __all__. Модули - пакеты док говорит

Эти __init__.pyфайлы необходимы , чтобы Python лечить каталоги как содержащие пакеты; это сделано для предотвращения непреднамеренного скрытия действительными модулями каталогов с общим именем, например, строки, которые встречаются позже в пути поиска модулей. В простейшем случае это __init__.pyможет быть просто пустой файл, но он также может выполнить код инициализации для пакета или установить __all__переменную, как описано ниже.

...

Единственное решение для автора пакета - предоставить явный индекс пакета. Оператор импорта использует следующее соглашение: если __init__.pyкод пакета определяет именованный список __all__, он принимается за список имен модулей, которые должны быть импортированы при обнаружении импорта из пакета *. Автор пакета должен поддерживать этот список в актуальном состоянии, когда выходит новая версия пакета. Авторы пакетов также могут принять решение не поддерживать его, если они не видят использования для импорта * из своего пакета. Например, файл sounds/effects/__init__.pyможет содержать следующий код:

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

Это будет означать, что from sound.effects import *будут импортированы три названных субмодуля звукового пакета.


3

Это лучший способ, который я нашел до сих пор:

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

Используя importlibединственное, что вам нужно добавить, это

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

Это приводит к законной проблеме Mypy error: Type of __all__ must be "Sequence[str]", not "List[Module]". Определение __all__не требуется, если используется этот import_moduleоснованный подход.
— Acumenus

1

Посмотрите на модуль pkgutil из стандартной библиотеки. Это позволит вам делать именно то, что вы хотите, если у вас есть __init__.pyфайл в каталоге. __init__.pyФайл может быть пустым.


1

Для этого я создал модуль, который не зависит __init__.py(или любой другой вспомогательный файл) и заставляет меня печатать только следующие две строки:

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

Не стесняйтесь повторно использовать или внести свой вклад: http://gitlab.com/aurelien-lourot/importdir


0

Просто импортируйте их с помощью importlib и добавьте их __all__( addдействие необязательно) в recurse- __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?
— Jeppe

извините, что пропустил это, теперь исправлено. Это расширение файла Python, который вы хотите импортировать, обычноpy
— Чейни,

0

Когда from . import *это недостаточно хорошо, это улучшение по сравнению с ответом Теда . В частности, использование __all__этого подхода не является необходимым.

"""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()он предназначен для того, чтобы избежать повторного импорта модуля, если он уже импортирован, так как это может привести к циклическому импорту.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.