Jak załadować wszystkie moduły do ​​folderu?

269

Czy ktoś mógłby mi zapewnić dobry sposób na import całego katalogu modułów?
Mam taką strukturę:

/Foo
    bar.py
    spam.py
    eggs.py

Próbowałem po prostu przekonwertować go na pakiet, dodając __init__.pyi wykonując, from Foo import *ale nie działało to tak, jak chciałem.

Evan Fosmark
źródło
2
Sposób inicjowania .py jest całkiem poprawny. Czy możesz opublikować kod, który nie działał?
balpha
9
Czy potrafisz zdefiniować „nie działał”? Co się stało? Jaki komunikat o błędzie dostałeś?
S.Lott
Czy jest to Python, czy zalecane? „Jawne jest lepsze niż niejawne”.
yangmillstheory
Wyraźne jest rzeczywiście lepsze. Ale czy naprawdę chciałbyś zająć się tymi irytującymi komunikatami „nie znaleziono” za każdym razem, gdy dodajesz nowy moduł? Myślę, że jeśli masz katalog pakietu zawierający wiele małych funkcji modułu, to jest to najlepszy sposób, aby to zrobić. Moje założenia to: 1. moduł jest dość prosty 2. używasz pakietu do
utrzymywania

Odpowiedzi:

400

Wyświetl wszystkie .pypliki python ( ) w bieżącym folderze i umieść je jako __all__zmienne w__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')]
Anurag Uniyal
źródło
57
Zasadniczo więc mogę upuścić pliki Pythona do katalogu bez dalszej konfiguracji i zlecić wykonanie skryptu działającego gdzie indziej.
Evan Fosmark
5
@NiallDouglas ta odpowiedź jest na konkretne pytanie, które zadał OP, nie miał pliku zip, a pliki pyc można łatwo dołączyć, a ty zapominasz również .pyd lub .so libs itp.
Anurag Uniyal
35
Jedyne, co chciałbym dodać, to if not os.path.basename(f).startswith('_')lub przynajmniej if not f.endswith('__init__.py')do końca listy
Pykler
10
Aby uczynić go bardziej niezawodnym, upewnij się również, że os.path.isfile(f)jest True. To odfiltrowałoby zepsute dowiązania symboliczne i katalogi, takie jak somedir.py/(przypadek, przyznaję, ale nadal ...)
MestreLion
12
Dodaj from . import *po ustawieniu __all__jeśli chcesz Submoduły być dostępne przy użyciu .(np module.submodule1, module.submodule2itp).
ostrokach
133

Dodaj __all__zmienną do __init__.pyzawierającą:

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

Zobacz także http://docs.python.org/tutorial/modules.html

Stefan
źródło
163
Tak, tak, ale czy jest jakiś sposób, aby był dynamiczny?
Evan Fosmark
27
Połączenie os.listdir(), niektóre filtrowanie, usuwanie .pyrozszerzenia i __all__.
Nie działa dla mnie jako przykładowy kod tutaj github.com/namgivu/python-import-all/blob/master/error_app.py . Może coś tam brakuje?
Nam G VU
1
Przekonałem się sam - aby użyć zmiennej / obiektu zdefiniowanego w tych modułach, musimy skorzystać z pełnej ścieżki odniesienia, np moduleName.varName. Ref. stackoverflow.com/a/710603/248616
Nam G VU
1
@NamGVU: Ten kod w mojej odpowiedzi na powiązane pytanie spowoduje zaimportowanie wszystkich publicznych podmodułów do przestrzeni nazw pakietu.
martineau
54

Aktualizacja w 2017 r .: prawdopodobnie chcesz importlibzamiast tego użyć .

Zrób katalog Foo pakietem, dodając __init__.py. W tym __init__.pydodaj:

import bar
import eggs
import spam

Ponieważ chcesz, aby był dynamiczny (co może, ale nie musi być dobrym pomysłem), wypisz wszystkie pliki py z list dir i zaimportuj je za pomocą czegoś takiego:

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

Następnie z kodu wykonaj następujące czynności:

import Foo

Możesz teraz uzyskać dostęp do modułów za pomocą

Foo.bar
Foo.eggs
Foo.spam

itp. from Foo import *nie jest dobrym pomysłem z kilku powodów, w tym z powodu konfliktów nazw i utrudniania analizy kodu.

Lennart Regebro
źródło
1
Nieźle, ale nie zapominaj, że możesz również importować pliki .pyc i .pyo.
Evan Fosmark
7
thh, uważam za __import__hackish, myślę, że lepiej byłoby dodać nazwy, __all__a następnie umieścić from . import *na dole skryptu
freeforall tousez
1
Myślę, że to ładniejsze niż wersja globalna.
lpapp,
8
__import__nie jest do ogólnych zastosowań, jest używany przez interpreter, użyj importlib.import_module()zamiast tego.
Andrew_1510
2
Pierwszy przykład był bardzo pomocny, dzięki! Pod Python 3.6.4 musiałem zrobić from . import eggsitd w __init__.pyprzed Pythonie mógł importować. Ze tylko import eggsja dostać ModuleNotFoundError: No module named 'eggs'podczas próby import Foow main.pysię w katalogu powyżej.
Nick
41

Rozwijając odpowiedź Mihaila, uważam, że niehackerski sposób (jak w przypadku braku bezpośredniej obsługi ścieżek plików) jest następujący:

  1. stwórz pusty __init__.py plik podFoo/
  2. Wykonać
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')

Dostaniesz:

<module 'Foo.bar' from '/home/.../Foo/bar.pyc'>
<module 'Foo.spam' from '/home/.../Foo/spam.pyc'>
Luca Invernizzi
źródło
Jest to w większości sposób na poprawną odpowiedź - obsługuje archiwa ZIP, ale nie pisze init ani nie importuje. Zobacz automodinit poniżej.
Niall Douglas
1
Kolejna rzecz: powyższy przykład nie sprawdza sys.modules, aby sprawdzić, czy moduł jest już załadowany. Bez tego sprawdzenia powyższe załaduje moduł po raz drugi :)
Niall Douglas
3
Kiedy uruchamiam load_all_modules_from_dir ('Foo / bar') z twoim kodem, dostaję "RuntimeWarning: Moduł nadrzędny 'Foo / bar' nie został znaleziony podczas obsługi importu bezwzględnego" - aby go wyłączyć, muszę ustawić full_package_name = '.'. dirname.split (os.path.sep) + nazwa_pakietu]), a także zaimportuj Foo.bar
Alex Dupuy
Te RuntimeWarningwiadomości mogą być również unikać nie używając w ogóle full_package_name: importer.find_module(package_name).load_module(package_name).
Artfunkel,
Te RuntimeWarningmogą być również unikać błędów (w niewątpliwie brzydki sposób) importując dominującej (AKA dirname). Jednym ze sposobów na to jest - if dirname not in sys.modules: pkgutil.find_loader(dirname).load_module(dirname). Oczywiście działa to tylko wtedy, gdy dirnamejest ścieżką względną jednoskładnikową; bez cięć. Osobiście wolę podejście Artfunkela polegające na użyciu podstawowej nazwy_pakietu.
dannysauer
31

Python, dołącz wszystkie pliki do katalogu:

Dla początkujących, którzy po prostu nie mogą go uruchomić i potrzebują trzymania się za ręce.

  1. Utwórz folder / home / el / foo i utwórz plik main.pyw katalogu / home / el / foo Umieść tam ten kod:

    from hellokitty import *
    spam.spamfunc()
    ham.hamfunc()
  2. Utwórz katalog /home/el/foo/hellokitty

  3. Utwórz plik __init__.pyponiżej /home/el/foo/hellokittyi umieść tam kod:

    __all__ = ["spam", "ham"]
  4. Utwórz dwa pliki Pythona: spam.pyi ham.pyponiżej/home/el/foo/hellokitty

  5. Zdefiniuj funkcję w spam.py:

    def spamfunc():
      print("Spammity spam")
  6. Zdefiniuj funkcję wewnątrz ham.py:

    def hamfunc():
      print("Upgrade from baloney")
  7. Uruchom:

    el@apollo:/home/el/foo$ python main.py 
    spammity spam
    Upgrade from baloney
Eric Leschinski
źródło
1
Nie tylko dla początkujących, ale także doświadczonych deweloperów Python, którzy lubią jasne odpowiedzi. Dzięki.
Michael Scheper,
Ale używanie import *jest uważane za złą praktykę kodowania w języku Python. Jak to zrobić bez tego?
Rachael Blake
16

Sam zmęczyłem się tym problemem, więc napisałem pakiet o nazwie automodinit, aby go naprawić. Możesz go pobrać ze strony http://pypi.python.org/pypi/automodinit/ .

Użycie jest takie:

  1. Uwzględnij automodinitpakiet w swoich setup.pyzależnościach.
  2. Zastąp wszystkie pliki __init__.py, takie jak to:
__all__ = [„Zostanie przepisany”]
# Nie modyfikuj powyższej linii ani tej linii!
import automodinit
automodinit.automodinit (__ name__, __file__, globals ())
del automodinit
# Wszystko, co chcesz, może iść tutaj, nie zostanie zmodyfikowane.

Otóż ​​to! Odtąd importowanie modułu ustawi __all__ na listę plików .py [co] w module, a także będzie importować każdy z tych plików tak, jakbyś wpisał:

for x in __all__: import x

Dlatego efekt „z importu M *” odpowiada dokładnie „importowi M”.

automodinit chętnie korzysta z archiwów ZIP i dlatego jest bezpieczny dla ZIP.

Niall

Niall Douglas
źródło
pip nie może pobrać automodinit, ponieważ nic nie zostało na niego przesłane.
kanzure
Dzięki za zgłoszenie błędu w github. Naprawiłem to w wersji 0.13. Niall
Niall Douglas
11

Wiem, że aktualizuję dość stary post i próbowałem go użyć automodinit, ale dowiedziałem się, że proces instalacji dla Python3 jest uszkodzony. Tak więc, w oparciu o odpowiedź Lucy, znalazłem prostszą odpowiedź - która może nie działać z .zip - na ten problem, więc pomyślałem, że powinienem ją tutaj udostępnić:

w __init__.pymodule od yourpackage:

#!/usr/bin/env python
import os, pkgutil
__all__ = list(module for _, module, _ in pkgutil.iter_modules([os.path.dirname(__file__)]))

oraz w ramach innego pakietu poniżej yourpackage:

from yourpackage import *

Następnie załadujesz wszystkie moduły umieszczone w pakiecie, a jeśli napiszesz nowy moduł, zostanie on również zaimportowany automatycznie. Oczywiście, używaj tego rodzaju rzeczy ostrożnie, z wielkimi mocami wiąże się wielka odpowiedzialność.

zmo
źródło
6
import pkgutil
__path__ = pkgutil.extend_path(__path__, __name__)
for imp, module, ispackage in pkgutil.walk_packages(path=__path__, prefix=__name__+'.'):
  __import__(module)
Ricky
źródło
5

Zetknąłem się również z tym problemem i to było moje rozwiązanie:

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

Ta funkcja tworzy plik (w podanym folderze) o nazwie __init__.py, który zawiera __all__zmienną, która przechowuje każdy moduł w folderze.

Na przykład mam folder o nazwie, Test który zawiera:

Foo.py
Bar.py

Więc w skrypcie chcę, aby moduły zostały zaimportowane, napiszę:

loadImports('Test/')
from Test import *

Spowoduje to zaimportowanie wszystkiego, Testa __init__.pyplik w Testbędzie teraz zawierać:

__all__ = ['Foo','Bar']
Pingwin Ostrze
źródło
idealnie, dokładnie to, czego szukam
uma mahesh
4

Przykład Anuraga z kilkoma poprawkami:

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
źródło
4

Odpowiedź Anurag Uniyal z sugerowanymi ulepszeniami!

#!/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  
Eduardo Lucio
źródło
3

Zobacz, jakie są Twoje __init__.pydefinicje __all__. Moduły - pakiety doc mówi

Te __init__.pypliki są wymagane, aby Pythona traktować jak katalogi zawierające pakiety; Ma to na celu zapobieżenie przypadkowemu ukryciu przez katalogi o wspólnej nazwie, takiej jak łańcuch, prawidłowych modułów pojawiających się później na ścieżce wyszukiwania modułów. W najprostszym przypadku __init__.pymoże być tylko pustym plikiem, ale może również wykonać kod inicjujący pakiet lub ustawić__all__ zmienną, opisaną później.

...

Jedynym rozwiązaniem jest podanie przez autora jawnego indeksu pakietu. Instrukcja importu używa następującej konwencji: jeśli __init__.pykod pakietu definiuje nazwaną listę __all__, przyjmuje się, że jest to lista nazw modułów, które powinny zostać zaimportowane po napotkaniu z importu pakietu *. Od autora pakietu zależy, czy ta lista będzie aktualna, kiedy zostanie wydana nowa wersja pakietu. Autorzy pakietów mogą również zdecydować, że nie będą go obsługiwać, jeśli nie widzą potrzeby importowania * ze swojego pakietu. Na przykład plik sounds/effects/__init__.pymoże zawierać następujący kod:

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

Oznaczałoby to, from sound.effects import *że zaimportujemy trzy nazwane podmoduły pakietu dźwiękowego.

gimel
źródło
3

To najlepszy sposób, jaki do tej pory znalazłem:

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())
Przeklęty
źródło
2

Korzystanie importlibjedyną rzeczą, którą muszę dodać jest

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
przetrząsać
źródło
Prowadzi to do legalnego wydania mypy: error: Type of __all__ must be "Sequence[str]", not "List[Module]". Definiowanie __all__nie jest wymagane, jeśli import_modulestosowane jest takie podejście.
Acumenus
1

Spójrz na moduł pkgutil ze standardowej biblioteki. Pozwoli ci to robić dokładnie to, co chcesz, o ile masz __init__.pyplik w katalogu. __init__.pyPlik może być pusty.

Mihail Mihaylov
źródło
1

Stworzyłem do tego moduł, który nie polega na __init__.py(ani na żadnym innym pliku pomocniczym) i każe mi pisać tylko następujące dwie linie:

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

Zapraszam do ponownego użycia lub współtworzenia: http://gitlab.com/aurelien-lourot/importdir

Aurelien
źródło
0

Wystarczy zaimportować je za pomocą importlib i dodać je __all__( addakcja jest opcjonalna) w rekursie w __init__.pypakiecie.

/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
Cheney
źródło
Gdzie jest zdefiniowane pyfile_extes?
Jeppe
przepraszam za spudłowanie, teraz naprawione. Jest to rozszerzenie pliku python, który chcesz zaimportować, zwykle po prostupy
Cheney
0

Gdy from . import *nie jest wystarczająco dobry, jest to poprawa w stosunku do odpowiedzi Teda . W szczególności zastosowanie __all__tego podejścia nie jest konieczne.

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

Należy pamiętać, że module_name not in globals()ma to na celu uniknięcie ponownego importowania modułu, jeśli jest już zaimportowany, ponieważ może to spowodować ryzyko cyklicznego importu.

Acumenus
źródło