有人可以为我提供导入整个模块目录的好方法吗?
我有这样的结构:

/Foo
    bar.py
    spam.py
    eggs.py


我试图将其转换为通过添加__init__.py并执行from Foo import *来打包,但它并没有达到我希望的方式。

评论

您可以定义“无效”吗?发生了什么?您收到什么错误消息?

是Pythonic还是推荐的? “显式优于隐式。”

显式确实更好。但是,您是否真的想在每次添加新模块时解决这些烦人的“未找到”消息。我认为,如果您有一个包含许多小模块功能的软件包目录,那么这是解决问题的最佳方法。我的假设是:1.模块相当简单2.您可以使用软件包进行代码整洁

#1 楼

列出当前文件夹中的所有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')]


评论


基本上,这样我就可以将python文件放到没有进一步配置的目录中,并让它们在其他地方运行的脚本执行。

– Evan Fosmark
09年6月30日在1:29

@NiallDouglas这个答案是OP提出的一个具体问题,他没有一个zip文件,可以轻松地包含pyc文件,而您也忘记了.pyd或.so libs等

–阿努拉格(Anurag Uniyal)
2012年5月5日,3:26

我唯一要添加的是如果不是os.path.basename(f).startswith('_'),或者至少是如果没有f.endswith('__ init__.py')到列表理解的末尾

–派克勒
13年2月28日在21:12



为了使其更健壮,还请确保os.path.isfile(f)为True。那样可以过滤掉损坏的符号链接和目录,例如somedir.py/(我承认是拐角大小写,但仍然...)

–MestreLion
2013年11月5日14:51



从添加。如果您希望使用子模块可用,请在设置__all__后导入*。 (例如,作为module.submodule1,module.submodule2等)。

– ostrokach
16年5月8日在15:21



#2 楼

__all__变量添加到包含以下内容的__init__.py

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


另请参见http://docs.python.org/tutorial/modules.html

评论


是的,是的,但是有什么办法可以使它动态化?

– Evan Fosmark
09年6月29日在9:52

os.listdir()的组合,一些过滤,.py扩展名和__all__的剥离。

–user234932
2014年9月8日16:32

不适用于我作为示例代码在github.com/namgivu/python-import-all/blob/master/error_app.py。也许我想念那里的东西?

– Nam G VU
17年5月30日在6:08

我发现自己-要使用这些模块中定义的变量/对象,我们必须使用完整的引用路径,例如moduleName.varName参考。 stackoverflow.com/a/710603/248616

– Nam G VU
17年5月30日在6:34

@NamGVU:我对相关问题的回答中的这段代码会将所有公共子模块的名称导入到程序包的名称空间中。

–马丁内
17-10-27在18:01

#3 楼

2017年更新:您可能想改用importlib

通过添加__init__.py将Foo目录打包。在该__init__.py中添加:

import bar
import eggs
import spam


由于您希望它是动态的(可能不是一个好主意),所以列出所有带有dir目录的py文件并导入它们然后使用如下代码:

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 *并不是一个好主意,原因有很多,其中包括名称冲突以及难以分析代码。

评论


不错,但是请不要忘记您也可以导入.pyc和.pyo文件。

– Evan Fosmark
09年6月30日在1:41

tbh,我发现__import__有点黑,我认为最好将名称添加到__all__,然后再从中放入。在脚本底部导入*

– freeforall tousez
14年8月26日在22:30

我认为这比全局版本更好。

–lpapp
2014年9月3日14:07

__import__不是用于一般用途,它由解释器使用,请改用importlib.import_module()。

–Andrew_1510
16年5月7日在2:48



第一个例子非常有帮助,谢谢!在Python 3.6.4下,我必须从开始。在Python可以导入之前,先在__init__.py中导入鸡蛋等。仅使用导入鸡蛋,当尝试在上述目录的main.py中导入Foo时,我得到ModuleNotFoundError:没有名为“ eggs”的模块。

–尼克
18-2-10在9:56



#4 楼

扩展Mihail的答案,我相信这种非骇客的方式(例如,不直接处理文件路径)如下:


__init__.py下创建一个空的Foo/文件

执行

 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也不导入。请参阅下面的automodinit。

–尼尔·道格拉斯(Niall Douglas)
2012年3月5日,下午2:26

另一件事:上面的示例未检查sys.modules以查看模块是否已加载。如果没有检查,以上内容将再次加载该模块:)

–尼尔·道格拉斯(Niall Douglas)
2012年3月5日21:00

当我用您的代码运行load_all_modules_from_dir('Foo / bar')时,我得到“ RuntimeWarning:处理绝对导入时未找到父模块'Foo / bar'”-要抑制这种情况,我必须设置full_package_name ='。'。join( dirname.split(os.path.sep)+ package_name]),然后导入Foo.bar

– Alex Dupuy
13年5月2日在6:37

也可以通过完全不使用full_package_name来避免这些RuntimeWarning消息:importer.find_module(package_name).load_module(package_name)。

– Artfunkel
16年11月4日在9:16

也可以通过导入父项(AKA目录名)来避免RuntimeWarning错误(以丑陋的方式)。一种方法是-如果目录名不在sys.modules中:pkgutil.find_loader(目录名).load_module(目录名)。当然,这仅在dirname是单组件相对路径时才有效;没有斜线。就个人而言,我更喜欢@Artfunkel的使用基本package_name的方法。

–dannysauer
17年1月30日在23:34



#5 楼

Python,将所有文件包含在目录下:

对于无法使用它的新手,需要双手握住。



创建一个文件夹/ home / el / foo并在/ home / el / foo下创建文件main.py将此代码放在此处:

from hellokitty import *
spam.spamfunc()
ham.hamfunc()


创建目录/home/el/foo/hellokitty

__init__.py下制作文件/home/el/foo/hellokitty并将代码放在其中:

__all__ = ["spam", "ham"]


spam.py下制作两个python文件:ham.py/home/el/foo/hellokitty

在spam.py内定义函数:

def spamfunc():
  print("Spammity spam")

在ham.py内定义函数:

def hamfunc():
  print("Upgrade from baloney")



运行它:

el@apollo:/home/el/foo$ python main.py 
spammity spam
Upgrade from baloney




评论


不仅适合新手,而且有经验的Python开发人员都喜欢明确的答案。谢谢。

–迈克尔·谢珀(Michael Scheper)
19/09/25 '16:58

但是使用import *被认为是不好的Python编码实践。没有这个,您该怎么做?

– Rachael Blake
2月24日15:34

#6 楼

我自己对此问题感到厌倦,所以写了一个名为automodinit的程序包来修复它。您可以从http://pypi.python.org/pypi/automodinit/获取。

用法是这样的:


automodinit包包含到您的setup.py依赖项中。
像这样替换所有__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


因此,“ from M import *”的效果与“ import M”完全匹配。

automodinit很高兴从ZIP存档内部运行,因此是ZIP安全的。

尼尔

评论


pip无法下载automodinit,因为pypi上没有上传任何内容。

– Kanzure
13年5月5日在8:01

感谢您在github上的错误报告。我已在v0.13中修复此问题。尼尔

–尼尔·道格拉斯(Niall Douglas)
13年2月9日在19:28

#7 楼

我知道我正在更新一个很旧的帖子,我尝试使用automodinit,但是发现它的设置过程对于python3来说是坏的。因此,基于Luca的答案,我想出了一个更简单的答案-可能不适用于.zip-此问题,所以我想应该在这里共享它:

__init__.pyyourpackage模块中:

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


并在yourpackage下面的另一个软件包中:放置在已加载的包中,如果您编写新模块,则也会自动导入。当然,小心谨慎地使用这类东西会带来巨大的责任。

#8 楼

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


#9 楼

我也遇到了这个问题,这是我的解决方案:

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


#10 楼

这是到目前为止我发现的最好方法:

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


#11 楼

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


#12 楼

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  


#13 楼

看到您的__init__.py定义了__all__。模块-软件包doc说

__init__.py文件是使Python将目录视为包含软件包所必需的;这样做是为了防止具有通用名称的目录(例如字符串)无意间隐藏了稍后在模块搜索路径中出现的有效模块。在最简单的情况下,__init__.py可以只是一个空文件,但它也可以为程序包执行初始化代码或设置__all__变量,如后所述。
...
唯一的解决方案是针对程序包作者提供包的显式索引。 import语句使用以下约定:如果软件包的__init__.py代码定义了名为__all__的列表,则将其视为遇到来自软件包import *时应导入的模块名称的列表。发行新版本的软件包时,软件包作者有责任使此列表保持最新。如果软件包作者看不到从软件包中导入*的用途,他们可能还会决定不支持它。例如,文件sounds/effects/__init__.py可能包含以下代码:
__all__ = ["echo", "surround", "reverse"]
,这意味着from sound.effects import *将导入声音包的三个命名子模块。


#14 楼

我为此创建了一个模块,该模块不依赖__init__.py(或任何其他辅助文件),仅键入以下两行即可:

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


随时重用或贡献:http://gitlab.com/aurelien-lourot/importdir

#15 楼

使用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问题:错误:__ all__的类型必须为“ Sequence [str]”,而不是“ List [Module]”。如果使用这种基于import_module的方法,则不需要定义__all__。

–触角
3月26日4:07

#16 楼

查看标准库中的pkgutil模块。只要目录中有__init__.py文件,它就可以让您执行所需的操作。 __init__.py文件可以为空。

#17 楼

只需通过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在哪里定义?

–杰普
18-10-28在12:24

很抱歉错过它,现已修复。它是您要导入的python文件的扩展名,通常只是py

–切尼
18-10-31在10:19

#18 楼

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()旨在避免重新导入模块(如果已导入),因为这可能会导致循环导入。