在函数内部导入是符合 Python 风格的吗?
- 2025-04-16 08:56:00
- admin 原创
- 16
问题描述:
PEP 8说:
导入总是放在文件的顶部,紧接着任何模块注释和文档字符串之后,以及模块全局变量和常量之前。
有时候,我会违反 PEP 8。有时我会在函数内部导入一些东西。一般来说,如果导入的内容只在一个函数中使用,我就会这样做。
有什么意见吗?
编辑(我觉得导入函数是个好主意的原因):
主要原因:可以使代码更加清晰。
在查看函数代码时,我可能会问自己:“xxx 是什么函数/类?”(xxx 在函数内部使用)。如果我所有的导入都在模块顶部,我必须去那里查看才能确定 xxx 是什么。这在使用 时尤其成问题。在函数中
from m import xxx
查看可能会告诉我更多信息。这取决于是什么:它是一个众所周知的顶级模块/包()吗?还是一个子模块/包()?m.xxx
`mimport m
from a.b.c import m`在某些情况下,在使用 xxx 的位置附近添加额外的信息(“xxx 是什么?”)可以使该功能更容易理解。
解决方案 1:
从长远来看,我认为您会喜欢将大多数导入放在文件顶部,这样您就可以一眼看出模块需要导入的内容有多复杂。
如果我要向现有文件添加新代码,我通常会在需要的地方进行导入,然后如果代码保留,我会通过将导入行移动到文件顶部来使其更加永久。
还有一点,我更喜欢ImportError
在运行任何代码之前得到一个异常 - 作为健全性检查,所以这是在顶部导入的另一个原因。
您可以使用 linter 来检查未使用的模块。
解决方案 2:
有两次我在这方面违反了 PEP 8:
循环导入:模块 A 导入模块 B,但模块 B 中的某些内容需要模块 A(尽管这通常表明我需要重构模块以消除循环依赖)
插入 pdb 断点:
import pdb; pdb.set_trace()
这很方便,因为我不想把它放在import pdb
我可能想要调试的每个模块的顶部,并且当我删除断点时很容易记住删除导入。
除了这两种情况之外,最好把所有内容都放在顶部。这样可以使依赖关系更清晰。
解决方案 3:
以下是我们使用的四个导入用例
import
(和from x import y
和import x as y
)在顶部导入选项。位于顶部。
import settings
if setting.something:
import this as foo
else:
import that as foo
条件导入。与 JSON、XML 库等一起使用。位于顶部。
try:
import this as foo
except ImportError:
import that as foo
动态导入。到目前为止,我们只有一个示例。
import settings
module_stuff = {}
module= __import__( settings.some_module, module_stuff )
x = module_stuff['x']
请注意,这种动态导入不会引入代码,而是引入用 Python 编写的复杂数据结构。它有点像腌制过的数据,只不过是我们手动腌制的。
这或多或少也位于模块的顶部
为了使代码更清晰,我们做了以下工作:
保持模块简短。
如果我把所有导入的代码都放在模块顶部,我就必须去那里查看它的名称。如果模块很短,这很容易做到。
在某些情况下,在靠近名称使用位置的地方添加额外的信息可以使函数更容易理解。如果模块很短,这很容易做到。
解决方案 4:
需要注意的是:不必要的导入会导致性能问题。所以,如果这是一个会被频繁调用的函数,最好将导入放在文件顶部。当然,这是一种优化,所以如果有充分的理由证明在函数内部导入比在文件顶部导入更清晰,那么在大多数情况下,这样做会更有利于提高性能。
如果你正在使用 IronPython,我听说最好在函数内部导入(因为在 IronPython 中编译代码可能很慢)。因此,你或许可以找到一种方法来导入函数内部。但除此之外,我认为不值得为了迎合惯例而这样做。
一般来说,如果导入仅在单个函数中使用,我就会这样做。
我想指出的另一点是,这可能是一个潜在的维护问题。如果你添加一个函数,它使用的模块之前只有一个函数使用,会发生什么?你会记得在文件顶部添加导入吗?还是要扫描每个函数来查找导入?
顺便说一下,在某些情况下,在函数内部导入是有意义的。例如,如果您想在 cx_Oracle 中设置语言,则需要在导入之前_
设置 NLS LANG 环境变量。因此,您可能会看到如下代码:
import os
oracle = None
def InitializeOracle(lang):
global oracle
os.environ['NLS_LANG'] = lang
import cx_Oracle
oracle = cx_Oracle
解决方案 5:
对于自测试模块,我以前就违反过这条规则。也就是说,它们通常只是用来支持,但我为它们定义了一个主函数,这样当你单独运行它们时,就可以测试它们的功能。在这种情况下,我有时会导入getopt
并cmd
直接运行主函数,因为我希望阅读代码的人清楚地知道,这些模块与模块的正常运行无关,只是为了测试而引入的。
解决方案 6:
来自关于两次加载模块的问题- 为什么不同时加载?
脚本顶部的导入将指示依赖关系,而函数中的另一个导入将使该函数更加原子化,同时似乎不会导致任何性能劣势,因为连续导入很便宜。
解决方案 7:
看一下 sqlalchemy 中使用的替代方法:依赖注入:
@util.dependencies("sqlalchemy.orm.query")
def merge_result(query, *args):
#...
query.Query(...)
注意导入的库是如何在装饰器中声明的,并将其作为参数传递给函数!
这种方法使代码更简洁,并且比语句快 4.5 倍import
!
基准:https://gist.github.com/kolypto/589e84fbcfb6312532658df2fabdb796
解决方案 8:
还有另一种(可能是“极端”)情况,其中import
内部很少使用的功能可能会有益:缩短启动时间。
我曾经遇到过这样的问题:在一个小型物联网服务器上运行一个相当复杂的程序,该程序接受来自串行线路的命令并执行操作,可能是非常复杂的操作。
将import
语句放在文件顶部意味着在服务器启动之前处理所有import
导入;由于列表包括jinja2
、lxml
和signxml
其他“重量级”(并且 SoC 不是很强大),这意味着在第一条指令实际执行之前的几分钟。
另一方面,通过将大多数导入放在函数中,我能够在几秒钟内让服务器在串行线路上“活跃”。当然,当真正需要这些模块时,我不得不付出代价(注:也可以通过import
在空闲时间运行后台任务来缓解这种情况)。
解决方案 9:
只要是import
且不是from x import *
,就应该把它们放在最上面。这样只会向全局命名空间添加一个名称,并且遵循 PEP 8 规范。另外,如果以后在其他地方需要它,也无需移动任何内容。
这没什么大不了的,但由于几乎没有区别,我建议按照 PEP 8 所说的去做。
解决方案 10:
在既是“普通”模块又可以执行(即具有if __name__ == '__main__':
-section)的模块中,我通常导入仅在主部分内执行模块时使用的模块。
例子:
def really_useful_function(data):
...
def main():
from pathlib import Path
from argparse import ArgumentParser
from dataloader import load_data_from_directory
parser = ArgumentParser()
parser.add_argument('directory')
args = parser.parse_args()
data = load_data_from_directory(Path(args.directory))
print(really_useful_function(data)
if __name__ == '__main__':
main()
扫码咨询,免费领取项目管理大礼包!