Как да заредя всички модули в папка?

Може ли някой да ми предостави добър начин за импортиране на цяла директория с модули?
Имам структура като тази:

/Foo
    bar.py
    spam.py
    eggs.py

Опитах просто да го конвертирам в пакет, като добавих __init__.py и направих from Foo import *, но не работи както се надявах.


person Evan Fosmark    schedule 29.06.2009    source източник
comment
Можете ли да определите не работи? Какво стана? Какво съобщение за грешка получихте?   -  person S.Lott    schedule 29.06.2009
comment
Това Pythonic ли е или препоръчително? Явното е по-добро от скритото.   -  person yangmillstheory    schedule 01.10.2016
comment
Експлицитното наистина е по-добро. Но бихте ли искали наистина да се справите с тези досадни съобщения „не е намерено“ всеки път, когато добавите нов модул. Мисля, че ако имате директория с пакети, съдържаща много малки модулни функции, тогава това е най-добрият начин да го направите. Моите предположения са: 1. модулът е доста прост 2. използвате пакета за подреденост на кода   -  person Ido_f    schedule 05.12.2019


Отговори (19)


Избройте всички python (.py) файлове в текущата папка и ги поставете като __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')]
person Anurag Uniyal    schedule 29.06.2009
comment
По принцип, за да мога да пусна python файлове в директория без допълнителна конфигурация и да ги накарам да бъдат изпълнени от скрипт, изпълняван някъде другаде. - person Evan Fosmark; 30.06.2009
comment
@NiallDouglas този отговор е за конкретен въпрос, който OP зададе, той нямаше zip файл и pyc файловете могат да бъдат включени лесно, а вие също забравяте .pyd или .so libs и т.н. - person Anurag Uniyal; 05.03.2012
comment
Вярно е, че OP не пита за стартиране на вътрешна поддръжка на zip, но изглежда, че иска шаблонен код, който ще импортира съдържанието на директория. Този шаблон трябва да се справи с повечето често срещани ситуации - не е трудно, просто използвайте pkgutil, а не os.listdir(), като другия плакат по-долу. - person Niall Douglas; 06.03.2012
comment
Единственото нещо, което бих добавил е if not os.path.basename(f).startswith('_') или най-малкото if not f.endswith('__init__.py') в края на разбирането на списъка - person Pykler; 01.03.2013
comment
Ако сте инсталирали 'path.py' (pip install path.py), можете да използвате __all__ = [f.namebase for f in path(__file__).parent.glob('*.py')], което според мен изглежда по-чисто. Също така съм съгласен, че __init__.py трябва да се филтрира от списъка. - person Mr. B; 16.08.2013
comment
os.listdir() е добра алтернатива на glob.glob(): може би субективно, но IMHO е малко по-стандартен/четлив начин за получаване на файлове в директория. Или os.walk(), ако искате рекурсия - person MestreLion; 05.11.2013
comment
За да го направите по-стабилен, също се уверете, че os.path.isfile(f) е True. Това би филтрирало неработещи символни връзки и директории като somedir.py/ (с малки букви, признавам, но все пак...) - person MestreLion; 05.11.2013
comment
Как мога да добавя филтъра @MestreLion? Не виждам място за оператор if в кода по-горе... - person Tomáš Zato - Reinstate Monica; 07.09.2015
comment
@TomášZato добави условие if за разбиране на списъка - person Anurag Uniyal; 08.09.2015
comment
Добавете from . import * след настройка __all__, ако искате подмодулите да са достъпни с помощта на . (напр. като module.submodule1, module.submodule2 и т.н.). - person ostrokach; 08.05.2016
comment
Проверих отново днес и изглежда вече не работи stackoverflow.com/questions/1057431/ - person Nam G VU; 30.05.2017
comment
За да бъдете кросплатформени, искате да използвате os.path.join(), за да конструирате своя път, в случай че os.path.sep не е '/' на проблемната операционна система на потребителя (като Windows): modules = glob.glob(os.path.join(dirname(__file__), '*.py')) - person hobs; 20.04.2019

Добавете променливата __all__ към __init__.py, съдържаща:

__all__ = ["bar", "spam", "eggs"]

Вижте също http://docs.python.org/tutorial/modules.html

person stefanw    schedule 29.06.2009
comment
Да, да, но има ли някакъв начин да бъде динамичен? - person Evan Fosmark; 29.06.2009
comment
Комбинация от os.listdir(), малко филтриране, премахване на .py разширение и __all__. - person ; 08.09.2014
comment
Не работи за мен като примерен код тук github.com/namgivu/ python-import-all/blob/master/error_app.py . Може би пропускам нещо там? - person Nam G VU; 30.05.2017
comment
Разбрах сам - за да използваме променливата/обекта, дефиниран в тези модули, трябва да използваме пълния референтен път, напр. moduleName.varName реф. stackoverflow.com/a/710603/248616 - person Nam G VU; 30.05.2017
comment
@NamGVU: Този код в моя отговор на свързан въпрос ще импортира имената на всички публични подмодули в пакета пространство от имена. - person martineau; 27.10.2017

Актуализация през 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 * не е добра идея поради няколко причини, включително сблъсъци на имена и затрудняване на анализа на кода.

person Lennart Regebro    schedule 29.06.2009
comment
Не е лошо, но не забравяйте, че можете да импортирате и .pyc и .pyo файлове. - person Evan Fosmark; 30.06.2009
comment
tbh, намирам __import__ за хакерско, мисля, че би било по-добре да добавите имената към __all__ и след това да поставите from . import * в долната част на скрипта - person freeforall tousez; 27.08.2014
comment
Мисля, че това е по-хубаво от глобалната версия. - person lpapp; 03.09.2014
comment
Вместо del, можете да направите import os as _os - person theorifice; 08.03.2015
comment
__import__ не е за обща употреба, използва се от interpreter, вместо това използвайте importlib.import_module(). - person Andrew_1510; 07.05.2016
comment
importlib не съществуваше, когато написах този отговор, а import работи добре. Но да, можете да използвате importlib и в 2.7 и по-нови версии. - person Lennart Regebro; 20.05.2016
comment
за какво е del module и защо е извън блока for-цикъл? - person Reese; 19.04.2017
comment
За да изтриете променливата на модула, която иначе поддържа препратка към името на последния модул и честно казано, не знам защо я сложих там, всъщност не е много полезно. Във всеки случай този отговор е стар, вместо това използвайте importlib. :-) - person Lennart Regebro; 22.04.2017
comment
Първият пример беше много полезен, благодаря! Под Python 3.6.4 трябваше да направя from . import eggs и т.н. в __init__.py, преди Python да може да импортира. Само с import eggs получавам ModuleNotFoundError: No module named 'eggs', когато се опитвам да import Foo в main.py в директорията по-горе. - person Nick; 10.02.2018
comment
Каква е тази разлика между това и __all__=['bar,'eggs',ham']? Изглежда по-чисто и повече в съответствие с първоначалното намерение на изявлението за внос. Има ли още нещо? - person Ray Salemi; 09.04.2018
comment
Идеята тук е да се заредят всички модули в папка, което означава, че модулът ще бъде зареден просто ако бъде добавен към папката, без да се променя никакъв код. Съгласен съм, че обикновено е нещо, което трябва да се избягва. - person Lennart Regebro; 10.04.2018
comment
Моля, дефинирайте „днес“. Предполагам, че имате предвид от определена версия на Python? Който? Благодаря. - person Michael Scheper; 25.09.2019
comment
@MichaelScheper Всяка версия на Python, която включва importlib. И ако вашата версия не го прави, моля, надстройте. - person Lennart Regebro; 25.09.2019
comment
Този отговор беше много полезен! Но не работи веднага от кутията по някаква причина на Py3.5.6. Трябваше да добавя . към началото на низа module, за да работи това, в противен случай не даде модул с име ‹filename›` ImportError. Освен това, когато превключвате към importlib.import_module( ), трябва да добавите аргумента importlib.import_module( modulebase, parent=__name__ ), за да влезе в пространството на имената на модула. Кажете ми, ако искате пълен примерен код. - person Demis; 26.04.2020

Разширявайки отговора на Михаил, вярвам, че не-хакерският начин (както в, без директно обработване на файловите пътища) е следният:

  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'>
person Luca Invernizzi    schedule 19.12.2011
comment
Това е по-голямата част от пътя до правилния отговор - той обработва ZIP архиви, но не пише init, нито импортира. Вижте automodinit по-долу. - person Niall Douglas; 05.03.2012
comment
Друго нещо: горният пример не проверява sys.modules, за да види дали модулът вече е зареден. Без тази проверка горното ще зареди модула втори път :) - person Niall Douglas; 06.03.2012
comment
Когато стартирам load_all_modules_from_dir('Foo/bar') с вашия код, получавам RuntimeWarning: Родителският модул 'Foo/bar' не е намерен при обработка на абсолютно импортиране - за да потисна това, трябва да задам full_package_name = '.'.join(dirname. split(os.path.sep) + package_name]) и също импортирайте Foo.bar - person Alex Dupuy; 02.05.2013
comment
Тези RuntimeWarning съобщения също могат да бъдат избегнати, като изобщо не използвате full_package_name: importer.find_module(package_name).load_module(package_name). - person Artfunkel; 04.11.2016
comment
Грешките RuntimeWarning също могат да бъдат избегнати (по безспорно грозен начин) чрез импортиране на родителя (известен още като dirname). Един от начините да направите това е - if dirname not in sys.modules: pkgutil.find_loader(dirname).load_module(dirname). Разбира се, това работи само ако dirname е еднокомпонентен относителен път; без наклонени черти. Лично аз предпочитам подхода на @Artfunkel за използване на базовото име на пакет вместо това. - person dannysauer; 31.01.2017

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
    
person Eric Leschinski    schedule 24.12.2013
comment
Не само за начинаещи, но и за опитни разработчици на Python, които харесват ясни отговори. Благодаря. - person Michael Scheper; 25.09.2019
comment
Но използването на import * се счита за лоша практика за кодиране на Python. Как правиш това без това? - person Rachael Blake; 24.02.2020

Самият аз се уморих от този проблем, така че написах пакет, наречен 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

Следователно ефектът на „от М импортиране *“ съвпада точно с „импортиране на М“.

automodinit работи успешно от вътрешните ZIP архиви и следователно е безопасен за ZIP.

Найл

person Niall Douglas    schedule 05.03.2012
comment
pip не може да изтегли automodinit, защото няма нищо качено на pypi за него. - person kanzure; 05.02.2013
comment
Благодаря за доклада за грешка в github. Поправих това във v0.13. Найл - person Niall Douglas; 09.02.2013

Знам, че актуализирам доста стара публикация и се опитах да използвам 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 *

След това ще имате заредени всички модули, които са поставени в пакета, и ако напишете нов модул, той също ще бъде автоматично импортиран. Разбира се, използвайте този вид неща внимателно, с големите правомощия идват и големи отговорности.

person zmo    schedule 26.03.2016

Аз също се сблъсках с този проблем и това беше моето решение:

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']
person Penguinblade    schedule 30.07.2012

Това е най-добрият начин, който съм открил досега:

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())
person Farshid Ashouri    schedule 02.07.2014

Примерът на Anurag с няколко корекции:

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")]
person kzar    schedule 19.11.2014

Отговорът на 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  
person Eduardo Lucio    schedule 13.06.2014

Използвайки 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
person ted    schedule 26.11.2019
comment
Това води до легитимен проблем с mypy: error: Type of __all__ must be "Sequence[str]", not "List[Module]". Дефинирането на __all__ не е необходимо, ако се използва този базиран на import_module подход. - person Acumenus; 26.03.2020

Вижте, че вашият __init__.py дефинира __all__. В документа модули - пакети се казва

Файловете __init__.py са необходими, за да може Python да третира директориите като съдържащи пакети; това се прави, за да се предотврати неволно скриване на валидни модули от директории с общо име, като например низ, които се появяват по-късно в пътя за търсене на модула. В най-простия случай __init__.py може да бъде просто празен файл, но може също така да изпълни код за инициализация за пакета или да зададе променливата __all__, описана по-късно.

...

Единственото решение е авторът на пакета да предостави изричен индекс на пакета. Изявлението за импортиране използва следната конвенция: ако __init__.py кодът на пакет дефинира списък с име __all__, той се приема за списък с имена на модули, които трябва да бъдат импортирани, когато се срещне от import * на пакет. От автора на пакета зависи да поддържа този списък актуален, когато бъде пусната нова версия на пакета. Авторите на пакети може също да решат да не го поддържат, ако не виждат употреба за импортиране на * от техния пакет. Например файлът sounds/effects/__init__.py може да съдържа следния код:

__all__ = ["echo", "surround", "reverse"]

Това би означавало, че from sound.effects import * ще импортира трите именувани подмодула на звуковия пакет.

person gimel    schedule 29.06.2009

Създадох модул за това, който не разчита на __init__.py (или друг спомагателен файл) и ме кара да въвеждам само следните два реда:

import importdir
importdir.do("Foo", globals())

Чувствайте се свободни да използвате повторно или да допринесете: http://gitlab.com/aurelien-lourot/importdir

person Aurelien    schedule 18.11.2014

Вижте модула pkgutil от стандартната библиотека. Ще ви позволи да правите точно това, което искате, стига да имате __init__.py файл в директорията. Файлът __init__.py може да е празен.

person Mihail Mihaylov    schedule 08.02.2011

Когато from . import * не е достатъчно добър, това е подобрение спрямо отговора от ted. По-конкретно, използването на __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() има за цел да избегне повторно импортиране на модула, ако той вече е импортиран, тъй като това може да застраши циклично импортиране.

person Acumenus    schedule 26.03.2020

Бих искал да добавя към отговора на Anurag Uniyal. Можете да го направите още по-просто и да се отървете от много импортирания. Съдържание на файла __init__.py:

from os import listdir
from os.path import dirname
__all__ = [i[:-3] for i in listdir(dirname(__file__)) if not i.startswith('__') and i.endswith('.py')]
person Arnab Santra    schedule 18.07.2021

Просто ги импортирайте от 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
person Cheney    schedule 18.01.2018
comment
Къде е дефиниран pyfile_extes? - person Jeppe; 28.10.2018
comment
съжалявам, че го пропускам, сега е поправено. Това е разширението на python файла, който искате да импортирате, обикновено само py - person Cheney; 31.10.2018

Имах структура на вложена директория, т.е. имах множество директории в основната директория, която съдържаше модулите на Python.

Добавих следния скрипт към моя __init__.py файл, за да импортирам всички модули

import glob, re, os 

module_parent_directory = "path/to/the/directory/containing/__init__.py/file"

owd = os.getcwd()
if not owd.endswith(module_parent_directory): os.chdir(module_parent_directory)

module_paths = glob.glob("**/*.py", recursive = True)

for module_path in module_paths:
    if not re.match( ".*__init__.py$", module_path):
        import_path = module_path[:-3]
        import_path = import_path.replace("/", ".")
        exec(f"from .{import_path} import *")

os.chdir(owd)

Вероятно не е най-добрият начин да постигна това, но не можах да накарам нищо друго да работи за мен.

person Afsan Abdulali Gujarati    schedule 15.01.2021