如何在包中只导入没有 exec __init__.py 的子模块
Posted
技术标签:
【中文标题】如何在包中只导入没有 exec __init__.py 的子模块【英文标题】:How to only import sub module without exec __init__.py in the package 【发布时间】:2014-02-13 10:47:36 【问题描述】:从包中导入子模块时,包文件夹中的 __init__.py 文件将首先执行,如何禁用此功能。有时我只需要一个包中的一个功能,导入整个包有点重。
例如,pandas.io.clipboard
模块不依赖于 pandas 中的任何其他函数。
from pandas.io.clipboard import clipboard_get
会导入函数,也会导入所有 pandas 常用模块。是否有一些方法只导入剪贴板模块,因为它是我自己的应用程序文件夹中的一个模块。
【问题讨论】:
一般不回答这个问题,但您可以使用 pyperclip 代替 pandas 剪贴板模块。 【参考方案1】:没有,设计。如果您想在导入子模块时避免太多开销,您只需使用空的__init__.py
s 来定义包。这样,导入包的开销几乎为零。
如果pandas
没有不 这样做,那么您有没有 方法来导入pandas.io.clipboard
而无需先导入pandas
和io
。您可以做的,但是它是一个巨大的hack并且它是不等效的,是将clipboard
模块作为普通模块导入作为一个子模块。您只需找到安装pandas
的位置(例如/usr/lib/pythonX.Y/dist-packages/
)并将父包的路径插入sys.path
(在您的情况下为/usr/lib/pythonX.Y/dist-packages/pandas/io
)。然后您可以通过以下方式导入clipboard
包:
import clipboard
但请注意:
import clipboard
from pandas.io import clipboard as clipboard2
print(clipboard == clipboard2)
将打印False
。事实上,这样做会破坏很多代码,因为您从根本上破坏了import
机制假设的一些不变量。
特别是如果子模块确实使用相对导入引用了其他子模块,则导入将失败,并且在其他情况下它不会正确运行。另一个失败的例子是你必须处理腌制的物体。如果您使用导入为 pandas.io.clipboard
的模块腌制了一些对象,您将不能够使用如上所述导入的模块 clipboard
取消腌制它们。
总之,不要!我建议:
如果导入包所花费的时间不是一个真正的问题,请接受它。 或者:尝试寻找替代品。如果您只需要pandas.io.clipboard
而不需要pandas
的其余部分,那么您首先不应该使用pandas
,而应该使用仅实现clipboard
功能的较小包。
如果您查看pandas.util.clipboard
source code,您会发现它实际上只是pyperclip
模块版本1.7。您可以在site-packages
中添加此模块并使用它来代替pandas
提供的模块。事实上pandas
团队只是在源代码末尾添加了以下部分:
## pandas aliases
clipboard_get = paste
clipboard_set = copy
扩展一下为什么 python 导入是这样工作的。
正如你在 python 中所知道的那样,模块就是对象。 包也是模块,虽然不是每个模块都是包。当你导入一个包时:
import pandas.io.clipboard
Python 必须:
-
创建
module
实例pandas
创建module
实例io
并将其作为属性添加到pandas
创建module
实例clipboard
并将其作为属性添加到io
。
为了创建module
实例python必须执行模块中的代码。
表单的导入:
from pandas.io import clipboard
只是语法糖:
import pandas.io.clipboard
clipboard = pandas.io.clipboard
del pandas.io
请注意,在from
的情况下,clipboard
可以是module
/包,也可以只是在io
中定义的东西。为了检查这一点,解释器必须还导入io
,为此它必须还导入pandas
。
【讨论】:
有没有办法让包的__init__.py
知道如何它是导入的?例如,熊猫的__init__.py
有没有办法知道它是从from pandas.util import clipboard
与import pandas
的原始语句中加载的?这将允许包开发人员允许从不需要包其余部分的任何特定模块导入,例如 constants.py
模块。【参考方案2】:
我找到了一个使用sys.meta_path
钩住导入过程的方法:
import imp, sys
class DummyLoader(object):
def load_module(self, name):
names = name.split("__")
path = None
for name in names:
f, path, info = imp.find_module(name, path)
path = [path]
return imp.load_module(name, f, path[0], info)
def find_module(self, name, path=None):
if "__" in name and not name.startswith("__"):
return DummyLoader()
else:
return None
if not sys.meta_path:
sys.meta_path.append(DummyLoader())
else:
sys.meta_path[0] = DummyLoader()
使用“__”而不是“。”仅用于加载文件:
import pandas__util__clipboard as clip
或使用函数加载文件:
import imp
def load_module(name):
names = name.split(".")
path = None
for name in names:
f, path, info = imp.find_module(name, path)
path = [path]
return imp.load_module(name, f, path[0], info)
clip = load_module("pandas.util.clipboard")
【讨论】:
【参考方案3】:使用 Python3.5+,您可以直接从其路径导入源文件,而无需在包含该文件的目录中执行 __init__
。灵感来自official importlib example:
import importlib
import os
import pathlib
import sys
import types
def import_module_from_path(path: str | os.PathLike) -> types.ModuleType:
"""Import a module from the given path."""
module_path = pathlib.Path(path).resolve()
module_name = module_path.stem # 'path/x.py' -> 'x'
spec = importlib.util.spec_from_file_location(module_name, module_path)
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)
return module
# Import `my_module` without executing `/path/to/__init__.py`
my_module = import_module_from_path('/path/to/my_module.py')
一些注意事项:
sys.modules[module_name] = module
将覆盖具有相似名称的模块。未来的import module_name
调用将返回此模块。
即使文件已经导入也会执行。
您可以查看sys.modules
并添加一些逻辑来解决这些问题。
【讨论】:
我得到了AttributeError: module 'importlib' has no attribute 'util'
,但用from importlib import util
解决了它,然后使用util.spec_from_file_location()
和util.module_from_spec()
。【参考方案4】:
我尝试了这些方法,但无法让它们发挥作用。所以显然设计它不应该工作..
如果您必须这样做,请在您尝试导入的存储库中创建一个新分支,或初始化一个存储库:
git checkout -b without_init
..然后删除__init__.py
!
从您尝试导入的任何地方,您都可以检查 Python 是否在正确的分支上,如下所示:
import subprocess
print ("Current branch is:", subprocess.check_output(["git rev-parse --abbrev-ref HEAD"], shell=True).strip().decode())
>> without_init
【讨论】:
以上是关于如何在包中只导入没有 exec __init__.py 的子模块的主要内容,如果未能解决你的问题,请参考以下文章