Is it pythonic to import inside functions?

PEP 8 says:

  • Imports are always put at the top of the file, just after any module comments and docstrings, and before module globals and constants.

On occation, I violate PEP 8. Some times I import stuff inside functions. As a general rule, I do this if there is an import that is only used within a single function.

Any opinions?

EDIT (the reason I feel importing in functions can be a good idea):

Main reason: It can make the code clearer.

  • When looking at the code of a function I might ask myself: "What is function/class xxx?" (xxx being used inside the function). If I have all my imports at the top of the module, I have to go look there to determine what xxx is. This is more of an issue when using from m import xxx. Seeing m.xxx in the function probably tells me more. Depending on what m is: Is it a well-known top-level module/package (import m)? Or is it a sub-module/package (from a.b.c import m)?
  • In some cases having that extra information ("What is xxx?") close to where xxx is used can make the function easier to understand.
63317 次浏览

只要是 import而不是 from x import *你就应该把它们放在最上面。它只向全局名称空间添加一个名称,您只需使用 PEP8。另外,如果你以后在别的地方需要它,你不需要移动任何东西。

这没什么大不了的,但是因为这几乎没有什么不同,我建议按照 PEP 8所说的去做。

在这方面,我有两次违反了 PEP 8:

  • 循环导入: 模块 A 导入模块 B,但是模块 B 中的某些内容需要模块 A (尽管这通常表明我需要重构模块以消除循环依赖)
  • 插入 pdb 断点: import pdb; pdb.set_trace()这是一个方便的 b/c,我不想把 import pdb放在每个我可能想要调试的模块的顶部,而且在删除断点时很容易记住删除导入。

除了这两个案子,把一切都放在首位是个好主意。它使依赖关系更加清晰。

需要记住的一点是: 不必要的导入可能导致性能问题。因此,如果这是一个经常被调用的函数,最好将导入放在顶部。当然,这个 是一个优化,所以如果有一个有效的例子说明在函数内部导入比在文件顶部导入更加清晰,那么在大多数情况下,导入优于性能。

如果您正在使用 IronPython,我被告知最好导入函数内部(因为用 IronPython 编译代码会很慢)。这样,您就可以找到导入内部函数的方法。但除此之外,我认为这不值得与传统作斗争。

作为一般规则,如果有一个只在单个函数中使用的导入,我就这样做。

我想说的另一点是,这可能是一个潜在的维护问题。如果添加一个使用以前只有一个函数使用的模块的函数,会发生什么情况?您是否记得将导入添加到文件的顶部?还是要扫描导入的每个函数?

FWIW,在某些情况下,在函数内部导入是有意义的。例如,如果你想在 cx _ Oracle 中设置语言,你需要设置一个导入的 NLS_LANG 环境变量 之前。因此,您可能会看到这样的代码:

import os


oracle = None


def InitializeOracle(lang):
global oracle
os.environ['NLS_LANG'] = lang
import cx_Oracle
oracle = cx_Oracle

对于自我测试的模块,我以前打破过这个规则。也就是说,它们通常只用于支持,但是我为它们定义了一个 main,这样如果您自己运行它们,就可以测试它们的功能。在这种情况下,我有时只是在 main 中导入 getoptcmd,因为我想让读代码的人清楚,这些模块与模块的正常操作没有任何关系,只是包含在测试中。

下面是我们使用的四个导入用例

  1. import(以及 from x import yimport x as y)在顶部

  2. 选择进口。在顶部。

    import settings
    if setting.something:
    import this as foo
    else:
    import that as foo
    
  3. Conditional Import. Used with JSON, XML libraries and the like. At the top.

    try:
    import this as foo
    except ImportError:
    import that as foo
    
  4. Dynamic Import. So far, we only have one example of this.

    import settings
    module_stuff = {}
    module= __import__( settings.some_module, module_stuff )
    x = module_stuff['x']
    

    请注意,这种动态导入不会带来代码,但会带来复杂的代码 用 Python 编写的数据结构 只不过我们是用手腌的。

    这也是,或多或少,在一个模块的顶部


下面是我们如何使代码更加清晰:

  • 保持模块的短小。

  • 如果所有导入都在模块的顶部,那么我必须去那里查看,以确定名称是什么。如果模块很短,那就很容易做到。

  • 在某些情况下,在使用名称的地方附近拥有额外的信息可以使函数更容易理解。如果模块很短,那就很容易做到。

从长远来看,我认为你会喜欢把大部分导入放在文件的顶部,这样你就可以一眼看出你的模块有多复杂,它需要导入什么。

如果我要在现有文件中添加新代码,我通常会在需要的地方进行导入,如果代码保留下来,我会将导入行移到文件的顶部,从而使事情变得更加永久。

还有一点,我更喜欢在运行任何代码之前获得一个 ImportError异常——作为一个健全性检查,所以这是在顶部导入的另一个原因。

我使用 pyChecker检查未使用的模块。

两次加载模块的问题来看-为什么不两者兼顾呢?

脚本顶部的导入将指示依赖项和函数中的另一个导入,从而使该函数更具原子性,同时似乎不会造成任何性能缺陷,因为连续导入成本较低。

来看看 sql 炼金术中使用的替代方法: 依赖注入:

@util.dependencies("sqlalchemy.orm.query")
def merge_result(query, *args):
#...
query.Query(...)

注意导入的库是如何在装饰器中声明并传递 作为函数的参数的!

这种方法使代码更清晰,而且比 import语句更适用于 快了4.5倍

基准: https://gist.github.com/kolypto/589e84fbcfb6312532658df2fabdb796

在同时是“普通”模块并且可以执行的模块中(例如,有一个 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()

还有另外一种情况(可能是“拐角”) ,在很少使用的函数中使用 import可能是有益的: 缩短启动时间。

有一次,我碰到了这样的瓶颈: 在一个小型物联网服务器上运行一个相当复杂的程序,它接受来自串行的命令并执行操作,可能是非常复杂的操作。

import语句放在文件顶部,意味着在服务器启动之前处理 所有导入; 由于 import列表包括 jinja2lxmlsignxml和其他“重量级”(SoC 不是很强大) ,这意味着 几分钟在第一条指令实际执行之前。

OTOH 将大多数导入放在函数中,我能够让服务器在几秒钟内在串行上“活”起来。当然,当实际上需要这些模块时,我必须付出代价(注意: 这也可以通过在空闲时间生成一个后台任务执行 import来减轻)。