Як завантажити всі модулі в папку?


269

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

/Foo
    bar.py
    spam.py
    eggs.py

Я спробував просто перетворити його в пакет, додавши __init__.pyта зробивши, from Foo import *але він не працював так, як я сподівався.


2
Шлях init .py досить правильний. Чи можете ви опублікувати код, який не працював?
balpha

9
Чи можете ви визначити "не працював"? Що трапилось? Яке повідомлення про помилку ви отримали?
S.Lott

Це Pythonic чи рекомендується? "Явне краще, ніж неявне."
yangmillstheory

Явне дійсно краще. Але ви хотіли б вирішити ці набридливі повідомлення "не знайдені" щоразу, коли ви додаєте новий модуль. Я думаю, що якщо у вас є каталог пакунків, який містить багато невеликих модульних функцій, то це найкращий спосіб зробити це. Мої припущення: 1. модуль досить простий 2. ви використовуєте пакет для
охайності

Відповіді:


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 ця відповідь стосується конкретного запитання, яке задав ОП, у нього не було zip-файлів, а файли pyc можна легко включити, і ви також забудете .pyd або .so libs тощо
Anurag Uniyal

35
Єдине, що я хотів би додати, це if not os.path.basename(f).startswith('_')або принаймні if not f.endswith('__init__.py')до кінця розуміння списку
Pykler

10
Щоб зробити його більш надійним, переконайтесь, що os.path.isfile(f)це так True. Це дозволило б відфільтрувати зламані символьні посилання та каталоги на кшталт somedir.py/(кутовий випадок, я визнаю, але все ж ...)
MestreLion

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

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-файли зі списком 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
tbh, я знаходжу __import__хакі, я думаю, що було б краще додати імена, __all__а потім поставити from . import *внизу сценарію
freeforall tousez

1
Я думаю, що це приємніше, ніж версія в світі.
lpapp

8
__import__не для загального використання, воно використовується interpreter, importlib.import_module()а замість цього.
Андрій_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 і не імпортує. Дивіться автоматичний підхід нижче.
Найлл Дуглас

1
Інша справа: наведений вище приклад не перевіряє sys.modules, щоб перевірити, чи модуль вже завантажений. Без цієї перевірки вищесказаний модуль завантажить вдруге :)
Niall Douglas

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

Ці RuntimeWarningповідомлення також можна уникнути, якщо використовувати full_package_name взагалі: importer.find_module(package_name).load_module(package_name).
Artfunkel

Ці RuntimeWarningпомилки також можна уникнути (можливо , в потворному вигляді) шляхом імпорту батька (АКА DIRNAME). Один із способів зробити це - if dirname not in sys.modules: pkgutil.find_loader(dirname).load_module(dirname). Звичайно, це працює лише в тому випадку, якщо dirnameце однокомпонентний відносний шлях; ніяких косих. Особисто я віддаю перевагу @ Artfunkel підходу використовувати базове ім'я пакета.
dannysauer

31

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 (__ ім'я__, __file__, глобальні ())
del automodinit
# Все, що ви хочете, може продовжити тут, воно не буде змінено.

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

for x in __all__: import x

Тому ефект "від М імпорту *" точно відповідає "імпорту М".

automodinit із задоволенням працює з архівів ZIP, тому він безпечний.

Найл


pip не може завантажити automodinit, оскільки для цього нічого не завантажено на pypi.
kanzure

Дякуємо за повідомлення про помилку в github. Я виправив це в v0.13. Niall
Niall Douglas

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']

ідеально, саме те, що я дивлюся
uma mahesh

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дія необов'язково) повторно в __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.