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


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.