¿Cómo cargar todos los módulos en una carpeta?

269

¿Podría alguien proporcionarme una buena manera de importar un directorio completo de módulos?
Tengo una estructura como esta:

/Foo
    bar.py
    spam.py
    eggs.py

Traté de convertirlo en un paquete agregando __init__.pyy haciendo, from Foo import *pero no funcionó de la manera que esperaba.

Evan Fosmark
fuente
2
La forma init .py es bastante correcta. ¿Puedes publicar el código que no funcionó?
balpha
9
¿Puedes definir "no funcionó"? ¿Que pasó? ¿Qué mensaje de error recibiste?
S.Lott
¿Esto es Pythonic o recomendado? "Explícito es mejor que implícito".
yangmillstheory
Explícito es de hecho mejor. Pero, ¿realmente le gustaría abordar estos molestos mensajes 'no encontrados' cada vez que agrega un nuevo módulo? Creo que si tiene un directorio de paquetes que contiene muchas funciones de módulos pequeños, entonces esta es la mejor manera de hacerlo. Mis suposiciones son: 1. el módulo es bastante simple 2. usa el paquete para
ordenar el

Respuestas:

400

Enumere todos los .pyarchivos python ( ) en la carpeta actual y colóquelos como __all__variables en__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
fuente
57
Básicamente para que pueda soltar archivos de Python en un directorio sin más configuración y hacer que se ejecuten mediante un script que se ejecute en otro lugar.
Evan Fosmark el
55
@NiallDouglas esta respuesta es para una pregunta específica que OP hizo, no tenía un archivo zip y los archivos pyc se pueden incluir fácilmente, y se está olvidando de .pyd o .so libs, etc. también
Anurag Uniyal
35
Lo único que agregaría es, if not os.path.basename(f).startswith('_')o al menos, if not f.endswith('__init__.py')al final de la comprensión de la lista
Pykler
10
Para hacerlo más robusto, también asegúrese de que lo os.path.isfile(f)sea True. Eso filtraría enlaces simbólicos y directorios rotos como somedir.py/(caso de esquina, lo admito, pero aún así ...)
MestreLion
12
Agregue from . import *después de la configuración __all__si desea que los submódulos estén disponibles usando .(por ejemplo module.submodule1, como module.submodule2, etc.).
ostrokach
133

Agregue la __all__variable a __init__.pycontener:

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

Ver también http://docs.python.org/tutorial/modules.html

stefanw
fuente
163
Sí, sí, pero ¿hay alguna forma de que sea dinámico?
Evan Fosmark
27
Combinación de os.listdir()algo de filtrado, despojo de .pyextensión y __all__.
No funciona para mí como código de muestra aquí github.com/namgivu/python-import-all/blob/master/error_app.py . Tal vez extraño algo allí?
Nam G VU
1
Lo descubrí yo mismo: para usar la variable / objeto definido en esos módulos, tenemos que usar la ruta de referencia completa, por ejemplo, moduleName.varNameref. stackoverflow.com/a/710603/248616
Nam G VU
1
@NamGVU: este código en mi respuesta a una pregunta relacionada importará todos los nombres de submódulos públicos al espacio de nombres del paquete.
Martineau
54

Actualización en 2017: probablemente quieras usar importliben su lugar.

Haga que el directorio de Foo sea un paquete agregando un __init__.py. En ese __init__.pycomplemento:

import bar
import eggs
import spam

Como lo desea dinámico (que puede o no ser una buena idea), enumere todos los archivos py con directorio dir e impórtelos con algo como esto:

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

Luego, desde su código, haga esto:

import Foo

Ahora puede acceder a los módulos con

Foo.bar
Foo.eggs
Foo.spam

etc. from Foo import *no es una buena idea por varias razones, incluidos los conflictos de nombres y la dificultad de analizar el código.

Lennart Regebro
fuente
1
No está mal, pero no olvides que también puedes importar archivos .pyc y .pyo.
Evan Fosmark el
77
tbh, me parece __import__hackish, creo que sería mejor agregar los nombres __all__y luego ponerlos from . import *al final del guión
freeforall tousez
1
Creo que esto es mejor que la versión global.
lpapp
8
__import__no es para usos generales, se usa por interpreter, use importlib.import_module()en su lugar.
Andrew_1510
2
El primer ejemplo fue muy útil, ¡gracias! Bajo Python 3.6.4 tuve que hacer from . import eggsetc. __init__.pyantes de que Python pudiera importar. Con solamente import eggsconsigo ModuleNotFoundError: No module named 'eggs'cuando se trata de import Fooen el main.pyen el directorio anterior.
Nick
41

Ampliando la respuesta de Mihail, creo que la forma no hack (como no manejar las rutas de archivo directamente) es la siguiente:

  1. crear un __init__.pyarchivo vacío debajoFoo/
  2. Ejecutar
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')

Obtendrás:

<module 'Foo.bar' from '/home/.../Foo/bar.pyc'>
<module 'Foo.spam' from '/home/.../Foo/spam.pyc'>
Luca Invernizzi
fuente
Esta es la mayor parte del camino hacia una respuesta correcta: maneja archivos ZIP, pero no escribe init ni importa. Ver automodinit a continuación.
Niall Douglas
1
Otra cosa: el ejemplo anterior no comprueba sys.modules para ver si el módulo ya está cargado. Sin esa verificación, lo anterior cargará el módulo por segunda vez :)
Niall Douglas
3
Cuando ejecuto load_all_modules_from_dir ('Foo / bar') con su código, obtengo "RuntimeWarning: módulo principal 'Foo / bar' no encontrado mientras se maneja la importación absoluta" - para suprimir esto, tengo que configurar full_package_name = '.'. Join ( dirname.split (os.path.sep) + package_name]) y también importar Foo.bar
Alex Dupuy
Esos RuntimeWarningmensajes también pueden ser evitados por no usar full_package_name en absoluto: importer.find_module(package_name).load_module(package_name).
Artfunkel el
Los RuntimeWarningerrores también se pueden evitar (de una manera fea) importando el padre (nombre de directorio AKA). Una forma de hacerlo es - if dirname not in sys.modules: pkgutil.find_loader(dirname).load_module(dirname). Por supuesto, eso solo funciona si dirnamees una ruta relativa de un solo componente; sin cortes. Personalmente, prefiero el enfoque de @ Artfunkel de usar el nombre_paquete base en su lugar.
dannysauer
32

Python, incluye todos los archivos en un directorio:

Para los novatos que simplemente no pueden hacer que funcione, que necesitan que lo agarren de la mano.

  1. Cree una carpeta / home / el / foo y cree un archivo main.pyen / home / el / foo. Coloque este código allí:

    from hellokitty import *
    spam.spamfunc()
    ham.hamfunc()
    
  2. Hacer un directorio /home/el/foo/hellokitty

  3. Haga un archivo __init__.pydebajo /home/el/foo/hellokittyy ponga este código allí:

    __all__ = ["spam", "ham"]
  4. Hacer dos archivos de python: spam.pyy ham.pydebajo/home/el/foo/hellokitty

  5. Defina una función dentro de spam.py:

    def spamfunc():
      print("Spammity spam")
    
  6. Defina una función dentro de ham.py:

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

    el@apollo:/home/el/foo$ python main.py 
    spammity spam
    Upgrade from baloney
    
Eric Leschinski
fuente
1
No solo para novatos, sino también para desarrolladores experimentados de Python a quienes les gustan las respuestas claras. Gracias.
Michael Scheper
Pero el uso import *se considera una mala práctica de codificación de Python. ¿Cómo haces esto sin eso?
Rachael Blake
16

Me cansé de este problema yo mismo, así que escribí un paquete llamado automodinit para solucionarlo. Puede obtenerlo en http://pypi.python.org/pypi/automodinit/ .

El uso es así:

  1. Incluya el automodinitpaquete en sus setup.pydependencias.
  2. Reemplace todos los archivos __init__.py como este:
__all__ = ["Me volverán a escribir"]
# ¡No modifique la línea de arriba, o esta línea!
importar automodinit
automodinit.automodinit (__ name__, __file__, globals ())
del automodinit
# Cualquier otra cosa que desee puede ir aquí, no se modificará.

¡Eso es! A partir de ahora, la importación de un módulo establecerá __todos__ en una lista de archivos .py [co] en el módulo y también importará cada uno de esos archivos como si hubiera escrito:

for x in __all__: import x

Por lo tanto, el efecto de "from M import *" coincide exactamente con "import M".

automodinit es feliz corriendo desde dentro de archivos ZIP y, por lo tanto, es seguro para ZIP.

Niall

Niall Douglas
fuente
pip no puede descargar automodinit porque no hay nada cargado en pypi para ello.
kanzure
Gracias por el informe de error en github. He arreglado esto en v0.13. Niall
Niall Douglas
11

Sé que estoy actualizando una publicación bastante antigua, e intenté usarla automodinit, pero descubrí que su proceso de configuración está roto para python3. Entonces, según la respuesta de Luca, se me ocurrió una respuesta más simple, que podría no funcionar con .zip, para este problema, así que pensé que debería compartirlo aquí:

dentro del __init__.pymódulo de yourpackage:

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

y dentro de otro paquete a continuación yourpackage:

from yourpackage import *

Luego tendrá todos los módulos que se colocan dentro del paquete cargado, y si escribe un nuevo módulo, también se importará automáticamente. Por supuesto, use ese tipo de cosas con cuidado, con grandes poderes conlleva grandes responsabilidades.

zmo
fuente
6
import pkgutil
__path__ = pkgutil.extend_path(__path__, __name__)
for imp, module, ispackage in pkgutil.walk_packages(path=__path__, prefix=__name__+'.'):
  __import__(module)
Ricky
fuente
5

También me encontré con este problema y esta fue mi solución:

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

Esta función crea un archivo (en la carpeta proporcionada) llamado __init__.py, que contiene una __all__variable que contiene cada módulo en la carpeta.

Por ejemplo, tengo una carpeta llamada Test que contiene:

Foo.py
Bar.py

Entonces, en el script que quiero que se importen los módulos, escribiré:

loadImports('Test/')
from Test import *

Esto importará todo Testy el __init__.pyarchivo Testahora contendrá:

__all__ = ['Foo','Bar']
Penguinblade
fuente
perfecto, exactamente lo que estoy buscando
uma Mahesh
4

El ejemplo de Anurag con un par de correcciones:

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
fuente
4

¡Respuesta de Anurag Uniyal con mejoras sugeridas!

#!/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
fuente
3

Ver que tu __init__.pydefine __all__. Los módulos - paquetes doc dice

Los __init__.pyarchivos son necesarios para hacer que Python trate los directorios como paquetes que contienen; Esto se hace para evitar que los directorios con un nombre común, como cadena, oculten involuntariamente módulos válidos que se producen más adelante en la ruta de búsqueda del módulo. En el caso más simple, __init__.pypuede ser un archivo vacío, pero también puede ejecutar el código de inicialización del paquete o establecer la __all__variable, que se describe más adelante.

...

La única solución es que el autor del paquete proporcione un índice explícito del paquete. La declaración de importación utiliza la siguiente convención: si el __init__.pycódigo de un paquete define una lista nombrada __all__, se considera que es la lista de nombres de módulos que deben importarse cuando se encuentra desde la importación del paquete *. Depende del autor del paquete mantener esta lista actualizada cuando se lance una nueva versión del paquete. Los autores de paquetes también pueden decidir no admitirlo, si no ven un uso para importar * desde su paquete. Por ejemplo, el archivo sounds/effects/__init__.pypodría contener el siguiente código:

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

Esto significaría que from sound.effects import *importaría los tres submódulos nombrados del paquete de sonido.

gimel
fuente
3

Esta es la mejor manera que he encontrado hasta ahora:

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())
Farsheed
fuente
2

Usar importliblo único que tienes que agregar es

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
ted
fuente
Esto lleva a una cuestión legítima mypy: error: Type of __all__ must be "Sequence[str]", not "List[Module]". __all__No es necesario definir si import_modulese utiliza este enfoque basado.
Acumenus
1

Mire el módulo pkgutil de la biblioteca estándar. Le permitirá hacer exactamente lo que quiera siempre que tenga un __init__.pyarchivo en el directorio. El __init__.pyarchivo puede estar vacío.

Mihail Mihaylov
fuente
1

He creado un módulo para eso, que no se basa en __init__.py(ni en ningún otro archivo auxiliar) y me hace escribir solo las siguientes dos líneas:

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

Siéntase libre de reutilizar o contribuir: http://gitlab.com/aurelien-lourot/importdir

Aurelien
fuente
0

Simplemente impórtelos por importlib y agréguelos a __all__(la addacción es opcional) en forma recurrente en el __init__.pypaquete.

/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
fuente
¿Dónde se define pyfile_extes?
Jeppe
lo siento por perderlo, ahora arreglado. Es la extensión del archivo de Python que desea importar, generalmente solopy
Cheney
0

Cuando from . import *no es lo suficientemente bueno, esta es una mejora sobre la respuesta de ted . Específicamente, el uso de __all__no es necesario con este enfoque.

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

Tenga en cuenta que module_name not in globals()está destinado a evitar volver a importar el módulo si ya está importado, ya que esto puede arriesgar las importaciones cíclicas.

Acumenus
fuente