Как импортировать все подмодули? - PullRequest
32 голосов
/ 29 июля 2010

У меня структура каталогов выглядит следующим образом:

| main.py
| scripts
|--| __init__.py
   | script1.py
   | script2.py
   | script3.py

С main.py модуль scripts импортируется.Я попытался использовать pkgutils.walk_packages в сочетании с __all__, но, используя это, я могу импортировать только все подмодули непосредственно под main, используя from scripts import *.Я хотел бы получить их все под scripts.Какой самый чистый способ импортировать все подмодули scripts, чтобы я мог получить доступ к scripts.script1 из main?

РЕДАКТИРОВАТЬ: я сожалею, что я был немного расплывчатым.Я хотел бы импортировать подмодули во время выполнения, не указав их явно в __init__.py.Я могу использовать pkgutils.walk_packages, чтобы получить имена подмодулей (если кто-то не знает лучшего способа), но я не уверен в самом чистом способе использования этих имен (или, может быть, ImpImporters, которые возвращает walk_packages?) Для их импорта.

Ответы [ 9 ]

31 голосов
/ 29 июля 2010

Редактировать: Вот один из способов рекурсивного импорта всего во время выполнения ...

(Содержимое __init__.py в каталоге верхнего пакета)

import pkgutil

__all__ = []
for loader, module_name, is_pkg in  pkgutil.walk_packages(__path__):
    __all__.append(module_name)
    _module = loader.find_module(module_name).load_module(module_name)
    globals()[module_name] = _module

Я не использую __import__(__path__+'.'+module_name) здесь, так как это трудно правильно рекурсивно импортировать пакеты, используя его. Если у вас нет вложенных подпакетов и вы хотите избежать использования globals()[module_name], то это один из способов сделать это.

Возможно, есть лучший способ, но это лучшее, что я могу сделать, в любом случае.

Оригинальный ответ (Для контекста игнорируйте иное. Сначала я неправильно понял вопрос):

Как выглядит scripts/__init__.py? Это должно быть что-то вроде:

import script1
import script2
import script3
__all__ = ['script1', 'script2', 'script3']

Вы даже можете обойтись без определения __all__, но все (pydoc, если ничего больше) будет работать более чисто, если вы определите его, даже если это просто список того, что вы импортировали.

27 голосов
/ 29 августа 2014

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

Это хороший, чистый способ выполнения импорта модулей подпакета, который также должен быть переносимым, и он использует стандартную библиотеку lib для python 2.7+ / 3.x.

import importlib
import pkgutil


def import_submodules(package, recursive=True):
    """ Import all submodules of a module, recursively, including subpackages

    :param package: package (name or actual module)
    :type package: str | module
    :rtype: dict[str, types.ModuleType]
    """
    if isinstance(package, str):
        package = importlib.import_module(package)
    results = {}
    for loader, name, is_pkg in pkgutil.walk_packages(package.__path__):
        full_name = package.__name__ + '.' + name
        results[full_name] = importlib.import_module(full_name)
        if recursive and is_pkg:
            results.update(import_submodules(full_name))
    return results

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

# from main.py, as per the OP's project structure
import scripts
import_submodules(scripts)

# Alternatively, from scripts.__init__.py
import_submodules(__name__)
13 голосов
/ 01 августа 2014

Просто работает и позволяет относительный импорт внутри пакетов:

def import_submodules(package_name):
    """ Import all submodules of a module, recursively

    :param package_name: Package name
    :type package_name: str
    :rtype: dict[types.ModuleType]
    """
    package = sys.modules[package_name]
    return {
        name: importlib.import_module(package_name + '.' + name)
        for loader, name, is_pkg in pkgutil.walk_packages(package.__path__)
    }

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

__all__ = import_submodules(__name__).keys()
2 голосов
/ 10 апреля 2015

Не так чисто, как хотелось бы, но ни один из более чистых методов не работал для меня. Это достигает указанного поведения:

Структура каталогов:

| pkg
|--| __init__.py
   | main.py
   | scripts
   |--| __init__.py
      | script1.py
      | script2.py
      | script3.py

Где pkg/scripts/__init__.py пусто, а pkg/__init__.py содержит:

import importlib as _importlib
import pkgutil as _pkgutil
__all__ = [_mod[1].split(".")[-1] for _mod in
           filter(lambda _mod: _mod[1].count(".") == 1 and not 
                               _mod[2] and __name__ in _mod[1],
                  [_mod for _mod in _pkgutil.walk_packages("." + __name__)])]
__sub_mods__ = [".".join(_mod[1].split(".")[1:]) for _mod in
                filter(lambda _mod: _mod[1].count(".") > 1 and not 
                                    _mod[2] and __name__ in _mod[1],
                       [_mod for _mod in 
                        _pkgutil.walk_packages("." + __name__)])]
from . import *
for _module in __sub_mods__:
    _importlib.import_module("." + _module, package=__name__)

Хотя это грязно, оно должно быть портативным. Я использовал этот код для нескольких разных пакетов.

2 голосов
/ 05 марта 2012

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

  1. Включите пакет automodinit в ваши setup.py зависимости.
  2. Добавьте следующее в начало__init__.py файл:
__all__ = ["I will get rewritten"]
# Don't modify the line above, or this line!
import automodinit
automodinit.automodinit(__name__, __file__, globals())
del automodinit
# Anything else you want can go after here, it won't get modified.

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

for x in __all__: import x

Следовательно, эффектиз from M import * соответствует в точности import M.

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

1 голос
/ 09 сентября 2013

Я играл с Ответ Джо Кингтона и создал решение, которое использует globals и get/setattr и, следовательно, не нуждается в eval. Небольшое изменение состоит в том, что вместо непосредственного использования пакетов __path__ для walk_packages я использую родительский каталог пакетов, а затем импортирую только модули, начиная с __name__ + ".". Это было сделано для надежного получения всех подпакетов из walk_packages - в моем случае у меня был подпакет с именем test, который заставил pkgutil перебирать пакет test из библиотеки python; более того, использование __path__ не будет входить в подкаталоги пакетов. Все эти проблемы наблюдались при использовании jython и python2.5, приведенный ниже код пока тестируется только в jython.

Также обратите внимание, что в вопросе OP говорится только об импорте всех модулей из пакета, этот код также рекурсивно импортирует все пакеты.

from pkgutil import walk_packages
from os import path

__all__ = []
__pkg_prefix = "%s." % __name__
__pkg_path = path.abspath(__path__[0]).rsplit("/", 1)[0] #parent directory

for loader, modname, _ in walk_packages([__pkg_path]):
    if modname.startswith(__pkg_prefix):
        #load the module / package
        module = loader.find_module(modname).load_module(modname)
        modname = modname[len(__pkg_prefix):] #strip package prefix from name
        #append all toplevel modules and packages to __all__
        if not "." in modname:
            __all__.append(modname)
            globals()[modname] = module
        #set everything else as an attribute of their parent package
        else:
            #get the toplevel package from globals()
            pkg_name, rest = modname.split(".", 1)
            pkg = globals()[pkg_name]
            #recursively get the modules parent package via getattr
            while "." in rest:
                subpkg, rest = rest.split(".", 1)
                pkg = getattr(pkg, subpkg)
            #set the module (or package) as an attribute of its parent package
            setattr(pkg, rest, module)

В качестве будущего улучшения я попытаюсь сделать это динамическим с помощью __getattr__ ловушки на пакете, чтобы фактические модули импортировались только при обращении к ним ...

0 голосов
/ 08 мая 2016

В Python 3 вы можете поместить следующий код в файл scripts.__init__.py:

import os
import os.path as op

__all__ = [
    op.splitext(f)[0]  # remove .py extension
    for f in os.listdir(BASE_DIR)  # list contents of current dir
    if not f.startswith('_') and
    ((op.isfile(op.join(BASE_DIR, f)) and f.endswith('.py')) or
     (op.isdir(op.join(BASE_DIR, f)) and op.isfile(op.join(BASE_DIR, f, '__init__.py'))))
]

from . import *  # to make `scripts.script1` work after `import script`

Для получения дополнительной информации об импорте Python я рекомендую выступление Дэвида Бизли на PyCon 2015: https://youtu.be/0oTh1CXRaQ0

0 голосов
/ 01 октября 2013

Это хорошо работает для меня в Python 3.3. Обратите внимание, что это работает только для подмодулей, которые находятся в файлах в том же каталоге, что и __init__.py. Однако, при некоторой работе это может быть улучшено для поддержки подмодулей в каталогах.

from glob import iglob
from os.path import basename, relpath, sep, splitext

def import_submodules(__path__to_here):
    """Imports all submodules.
    Import this function in __init__.py and put this line to it:
    __all__ = import_submodules(__path__)"""
    result = []
    for smfile in iglob(relpath(__path__to_here[0]) + "/*.py"):
        submodule = splitext(basename(smfile))[0]
        importstr = ".".join(smfile.split(sep)[:-1])
        if not submodule.startswith("_"):
            __import__(importstr + "." + submodule)
            result.append(submodule)
    return result
0 голосов
/ 29 июля 2010

Я все время писал небольшую личную библиотеку и добавлял новые модули, поэтому я написал сценарий оболочки для поиска сценариев и создания __init__.py. Сценарий выполняется за пределами основного каталога для моего пакета, pylux.

Я знаю, что это, вероятно, не тот ответ, который вы ищете, но он послужил своей цели для меня и, возможно, был бы полезен для кого-то еще.

#!/bin/bash

echo 'Traversing folder hierarchy...'

CWD=`pwd`


for directory in `find pylux -type d -exec echo {} \;`;
do
    cd $directory
    #echo Entering $directory
    echo -n "" > __init__.py

    for subdirectory in `find . -type d -maxdepth 1 -mindepth 1`;
    do
        subdirectory=`echo $subdirectory | cut -b 3-`
        #echo -n '    ' ...$subdirectory
        #echo -e '\t->\t' import $subdirectory
        echo import $subdirectory >> __init__.py
    done

    for pyfile in *.py ;
    do
        if [ $pyfile = $(echo __init__.py) ]; then
            continue
        fi
        #echo -n '    ' ...$pyfile
        #echo -e '\t->\t' import `echo $pyfile | cut -d . -f 1`
        echo import `echo $pyfile | cut -d . -f 1` >> __init__.py
    done
    cd $CWD

done


for directory in `find pylux -type d -exec echo {} \;`;
do
    echo $directory/__init__.py:
    cat $directory/__init__.py | awk '{ print "\t"$0 }'
done
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...