Como carregar todos os módulos em uma pasta?

269

Alguém poderia me fornecer uma boa maneira de importar um diretório inteiro de módulos?
Eu tenho uma estrutura como esta:

/Foo
    bar.py
    spam.py
    eggs.py

Tentei convertê-lo em um pacote adicionando __init__.pye executando, from Foo import *mas não funcionou da maneira que eu esperava.

Evan Fosmark
fonte
2
A maneira init .py é bastante correta. Você pode postar o código que não funcionou?
balpha
9
Você pode definir "não funcionou"? O que aconteceu? Que mensagem de erro você recebeu?
285/09 S.Lott
É pitonico ou recomendado? "Explícito é melhor que implícito."
Yangmillstheory
Explícito é realmente melhor. Mas você realmente gostaria de abordar essas mensagens irritantes 'não encontradas' toda vez que adicionar um novo módulo. Eu acho que se você tiver um diretório de pacote contendo muitas pequenas funções de módulo, essa é a melhor maneira de fazer isso. Minhas suposições são: 1. o módulo é bastante simples 2. você usa o pacote para organizar o código
Ido_f 05/12/19

Respostas:

400

Liste todos os .pyarquivos python ( ) na pasta atual e coloque-os como __all__variáveis ​​em__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
fonte
57
Basicamente, para que eu possa soltar arquivos python em um diretório sem configuração adicional e executá-los por um script em outro local.
Evan Fosmark 30/06/09
5
@NiallDouglas esta resposta é para uma pergunta específica que pediu OP, ele não tem um arquivo zip e arquivos PYC podem ser incluídos facilmente, e você está esquecendo .pyd ou .so libs etc também
Anurag Uniyal
35
A única coisa que eu gostaria de acrescentar é if not os.path.basename(f).startswith('_')ou pelo menos if not f.endswith('__init__.py')até o fim da lista de compreensão
Pykler
10
Para torná-lo mais robusto, também certificar-se os.path.isfile(f)é True. Isso filtrar links simbólicos quebrados e diretórios como somedir.py/(canto caso, eu admito, mas ainda assim ...)
MestreLion
12
Adicione from . import *após a configuração __all__se desejar que os submódulos estejam disponíveis usando .(por exemplo module.submodule1, como module.submodule2, etc.).
Ostrokach # 8/16
133

Adicione a __all__variável a __init__.pyconter:

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

Veja também http://docs.python.org/tutorial/modules.html

stefanw
fonte
163
Sim, sim, mas existe alguma maneira de fazê-lo ser dinâmico?
Evan Fosmark
27
Combinação de os.listdir(), alguma filtragem, remoção de .pyextensão e __all__.
Não estou trabalhando para mim como exemplo de código aqui github.com/namgivu/python-import-all/blob/master/error_app.py . Talvez eu perca alguma coisa lá?
Nam G VU
1
Eu me descobri - para usar a variável / objeto definido nesses módulos, temos que usar o caminho de referência completo, por exemplo, moduleName.varNameref. stackoverflow.com/a/710603/248616
Nam G VU
1
@NamGVU: Este código na minha resposta a uma pergunta relacionada importará todos os nomes dos submódulos públicos para o espaço de nome do pacote.
27617 martineau
54

Atualização em 2017: você provavelmente deseja usar importlib.

Torne o diretório Foo um pacote adicionando um __init__.py. Nesse __init__.pyaditamento:

import bar
import eggs
import spam

Como você deseja que seja dinâmico (o que pode ou não ser uma boa idéia), liste todos os arquivos py com o diretório dir e importe-os com algo como isto:

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

Em seguida, no seu código, faça o seguinte:

import Foo

Agora você pode acessar os módulos com

Foo.bar
Foo.eggs
Foo.spam

etc. from Foo import *não é uma boa ideia por vários motivos, incluindo conflitos de nome e dificultando a análise do código.

Lennart Regebro
fonte
1
Nada mal, mas não esqueça que você também pode importar arquivos .pyc e .pyo.
Evan Fosmark
7
TBH, eu encontrar __import__hackish, eu acho que seria melhor para adicionar os nomes para __all__e em seguida, colocar from . import *na parte inferior do script
freeforall tousez
1
Eu acho que isso é melhor do que a versão glob.
Lpapp # /
8
__import__não é para usos gerais, é usado por interpreter, use em importlib.import_module()vez disso.
21416 Andrew_1510 2:16
2
O primeiro exemplo foi muito útil, obrigado! No Python 3.6.4, eu tinha que fazer from . import eggsetc. __init__.pyantes que o Python pudesse importar. Com apenas import eggsrecebo ModuleNotFoundError: No module named 'eggs'ao tentar import Foono main.pyno diretório acima.
1076 Nick
41

Expandindo a resposta de Mihail, acredito que a maneira não hackeada (como em não manipular os caminhos do arquivo diretamente) é a seguinte:

  1. crie um __init__.pyarquivo vazio emFoo/
  2. Executar
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')

Você terá:

<module 'Foo.bar' from '/home/.../Foo/bar.pyc'>
<module 'Foo.spam' from '/home/.../Foo/spam.pyc'>
Luca Invernizzi
fonte
Essa é a maneira mais comum de encontrar uma resposta correta - ele lida com arquivos ZIP, mas não grava init nem import. Veja automodinit abaixo.
Niall Douglas
1
Outra coisa: o exemplo acima não verifica sys.modules para ver se o módulo já está carregado. Sem essa verificação acima irá carregar o módulo pela segunda vez :)
Niall Douglas
3
Quando executo load_all_modules_from_dir ('Foo / bar') com seu código, recebo "RuntimeWarning: Módulo pai 'Foo / bar' não encontrado ao manipular importação absoluta" - para suprimir isso, para suprimir isso, preciso definir full_package_name = '.'. Join ( dirname.split (os.path.sep) + nome_pacote]) e também importar Foo.bar
Alex Dupuy
Essas RuntimeWarningmensagens também podem ser evitados por não usar full_package_name em tudo: importer.find_module(package_name).load_module(package_name).
Artfunkel #
Os RuntimeWarningerros também podem ser evitados (de uma maneira indiscutivelmente feia) importando o pai (nome do diretório AKA). Uma maneira de fazer isso é - if dirname not in sys.modules: pkgutil.find_loader(dirname).load_module(dirname). Obviamente, isso só funciona se dirnamefor um caminho relativo de componente único; sem barras. Pessoalmente, prefiro a abordagem do @ Artfunkel de usar a base package_name.
precisa saber é o seguinte
31

Python, inclua todos os arquivos em um diretório:

Para iniciantes que simplesmente não conseguem fazê-lo funcionar e que precisam de suas mãos.

  1. Crie uma pasta / home / el / foo e crie um arquivo main.pyem / home / el / foo Coloque este código lá:

    from hellokitty import *
    spam.spamfunc()
    ham.hamfunc()
  2. Crie um diretório /home/el/foo/hellokitty

  3. Crie um arquivo __init__.pyabaixo /home/el/foo/hellokittye coloque este código lá:

    __all__ = ["spam", "ham"]
  4. Crie dois arquivos python: spam.pye ham.pysob/home/el/foo/hellokitty

  5. Defina uma função dentro do spam.py:

    def spamfunc():
      print("Spammity spam")
  6. Defina uma função no ham.py:

    def hamfunc():
      print("Upgrade from baloney")
  7. Executá-lo:

    el@apollo:/home/el/foo$ python main.py 
    spammity spam
    Upgrade from baloney
Eric Leschinski
fonte
1
Não apenas para iniciantes, mas também para desenvolvedores Python experientes que gostam de respostas claras. Obrigado.
Michael Scheper
Mas usar import *é considerado uma prática ruim de codificação Python. Como você faz isso sem isso?
Rachael Blake
16

Eu mesmo me cansei desse problema, então escrevi um pacote chamado automodinit para corrigi-lo. Você pode obtê-lo em http://pypi.python.org/pypi/automodinit/ .

O uso é assim:

  1. Inclua o automodinitpacote em suas setup.pydependências.
  2. Substitua todos os arquivos __init__.py assim:
__all__ = ["Eu serei reescrito"]
# Não modifique a linha acima, ou esta linha!
importação automática
automodinit.automodinit (__ name__, __file__, globals ())
del automodinit
# Qualquer coisa que você quiser pode ir atrás daqui, não será modificado.

É isso aí! A partir de agora, a importação de um módulo definirá __all__ como uma lista de arquivos .py [co] no módulo e também importará cada um desses arquivos como se você tivesse digitado:

for x in __all__: import x

Portanto, o efeito de "from M import *" corresponde exatamente a "import M".

automodinit é feliz executando dentro de arquivos ZIP e, portanto, é seguro para ZIP.

Niall

Niall Douglas
fonte
O pip não pode baixar o automodinit porque não há nada carregado no pypi para ele.
kanzure
Obrigado pelo relatório de erros no github. Corrigi isso na v0.13. Niall
Niall Douglas
11

Sei que estou atualizando um post bastante antigo e tentei usá-lo automodinit, mas descobri que o processo de instalação está quebrado para o python3. Portanto, com base na resposta de Luca, criei uma resposta mais simples - que pode não funcionar com .zip - para esta questão, então achei que deveria compartilhá-la aqui:

dentro do __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__)]))

e dentro de outro pacote abaixo yourpackage:

from yourpackage import *

Então você terá todos os módulos que estão dentro do pacote carregados e, se você escrever um novo módulo, ele também será importado automaticamente. Obviamente, use esse tipo de coisa com cuidado, com grandes poderes e grandes responsabilidades.

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

Eu também encontrei esse problema e esta foi a minha solução:

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

Essa função cria um arquivo (na pasta fornecida) nomeado __init__.py, que contém uma __all__variável que contém todos os módulos da pasta.

Por exemplo, eu tenho uma pasta chamada Test que contém:

Foo.py
Bar.py

Portanto, no script, quero que os módulos sejam importados para o arquivo:

loadImports('Test/')
from Test import *

Isso importará tudo Teste o __init__.pyarquivo Testagora conterá:

__all__ = ['Foo','Bar']
Penguinblade
fonte
perfeito, exatamente o que eu estou procurando
Uma Mahesh
4

O exemplo de Anurag com algumas correções:

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

Resposta Anurag Uniyal com melhorias 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
fonte
3

Veja que o seu __init__.pydefine __all__. Os módulos - pacotes doc diz

Os __init__.pyarquivos são necessários para fazer o Python tratar os diretórios como contendo pacotes; isso é feito para impedir que diretórios com um nome comum, como string, ocultem acidentalmente módulos válidos que ocorrem posteriormente no caminho de pesquisa do módulo. No caso mais simples, __init__.pypode ser apenas um arquivo vazio, mas também pode executar o código de inicialização do pacote ou definir a __all__variável, descrita posteriormente.

...

A única solução é o autor do pacote fornecer um índice explícito do pacote. A instrução de importação usa a seguinte convenção: se o __init__.pycódigo de um pacote definir uma lista denominada __all__, será considerada a lista de nomes de módulos que devem ser importados quando da importação do pacote * for encontrada. Cabe ao autor do pacote manter essa lista atualizada quando uma nova versão do pacote for lançada. Os autores do pacote também podem decidir não dar suporte, se não virem um uso para importar * do pacote. Por exemplo, o arquivo sounds/effects/__init__.pypode conter o seguinte código:

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

Isso significaria que from sound.effects import *importaria os três submódulos nomeados do pacote de som.

gimel
fonte
3

Esta é a melhor maneira que encontrei até agora:

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
fonte
2

Usando importliba única coisa que você precisa adicionar é

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
fonte
Isto leva a uma questão mypy legítima: error: Type of __all__ must be "Sequence[str]", not "List[Module]". A definição __all__não é necessária se essa import_moduleabordagem baseada for usada.
Acumenus 26/03
1

Veja o módulo pkgutil da biblioteca padrão. Isso permitirá que você faça exatamente o que deseja, desde que tenha um __init__.pyarquivo no diretório O __init__.pyarquivo pode estar vazio.

Mihail Mihaylov
fonte
1

Eu criei um módulo para isso, que não depende __init__.py(ou qualquer outro arquivo auxiliar) e me faz digitar apenas as duas linhas a seguir:

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

Sinta-se à vontade para reutilizar ou contribuir: http://gitlab.com/aurelien-lourot/importdir

Aurelien
fonte
0

Apenas importe-os por importlib e adicione-os a __all__(a addação é opcional) em recursão no __init__.pypacote.

/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
fonte
Onde pyfile_extes é definido?
Jeppe #
desculpe por falta, agora corrigido. É a extensão de arquivo de python que deseja importar, geralmente apenaspy
Cheney
0

Quando from . import *não é bom o suficiente, é uma melhoria em relação à resposta de ted . Especificamente, o uso de __all__não é necessário com essa abordagem.

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

Observe que module_name not in globals()se destina a evitar a reimportação do módulo se ele já estiver importado, pois isso pode arriscar importações cíclicas.

Acumenus
fonte