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

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

stefanw
источник
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 *

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

ZMO
источник
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']
Penguinblade
источник
идеально, именно то, что я ищу
Ума Махеш
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")]
kzar
источник
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())
Farsheed
источник
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()он предназначен для того, чтобы избежать повторного импорта модуля, если он уже импортирован, так как это может привести к циклическому импорту.

Акаменус
источник