2010-07-29 3 views
26

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

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

EDIT: Мне жаль, что я был немного расплывчатым. Я хотел бы импортировать подмодули во время выполнения, не указав их явно в __init__.py. Я могу использовать pkgutils.walk_packages, чтобы получить имена подмодулей (если кто-то не знает лучшего способа), но я не уверен в самом чистом способе использования этих имен (или, возможно, ImpImporters, который возвращает walk_packages?) Для их импорта.

+1

будет pkgutil работу, если вы развертываете приложение в виде застежки-молнии? вы можете взглянуть на «import pkg_resources», на всякий случай –

ответ

25

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

Он использует EXEC, так что почти наверняка лучший способ, но он работает (даже для произвольно вложенных суб-пакетов, я думать).

(Содержание __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) 
    exec('%s = module' % module_name) 

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

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

Оригинал ответа (для контекста, игнорировать othwerwise я неправильно понял вопрос изначально.):

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

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

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

+1

Извините, что не объяснял полностью. Я отредактировал главный пост. Я хочу динамически импортировать подмодули во время выполнения. – linkmaster03

+0

@ linkmaster03 - Извините за недоразумение. См. Изменения выше ... Это уродливо, но оно работает. Однако, вероятно, лучший способ. –

+0

Спасибо! :) Я обнаружил, что использование '__import__' и не нужно импортировать скрипты по их именам, которые действительно были хорошо разработаны, потому что я мог просто перебирать модули без необходимости их имен. – linkmaster03

1

Я писал небольшую личную библиотеку и добавлял новые модули все время, поэтому я написал сценарий оболочки для поиска скриптов и создания __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 
+1

Невозможно написать такую ​​же логику в '__init __. Py'? Используя 'os.listdir' и' __import__'? – cji

+0

Хм. Полагаю, так, но мне пришлось бы изучить его немного больше. – physicsmichael

2

Я сам устал от этой проблемы, поэтому я написал пакет под названием 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

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

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

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

Это хорошо работает для меня в 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 
+1

это не будет работать в яйцах и почтовых упаковках – kolypto

9

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

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

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

Это хороший, чистый способ выполнения импорта модулей подпакета, а также должен быть переносимым, и он использует стандартную 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__) 
2

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

Каталог:

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

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

+0

: - | Просто любопытно - а как насчет чистых методов не получилось? –

+0

Tbh, я не помню .. извините! Ограничение может даже не существовать/применяться больше, поэтому, вероятно, стоит проверить. Я не уверен, решает ли он этот вопрос, но в настоящее время я использую '__all__ = [_mod [1] для _mod в _pkgutil.iter_modules (__ path__), если не _mod [2]]'. Я почти уверен, что функция 'iter_modules' не существовала тогда. – user2561747

0

В 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

Смежные вопросы