导入内部函数是pythonic吗?

Posted

技术标签:

【中文标题】导入内部函数是pythonic吗?【英文标题】:Is it pythonic to import inside functions? 【发布时间】:2010-11-04 16:10:58 【问题描述】:

PEP 8 说:

导入总是放在文件的顶部,就在任何模块之后 cmets 和 docstrings,以及模块全局变量和常量之前。

有时,我违反了 PEP 8。有时我在函数中导入东西。作为一般规则,如果有一个只在单个函数中使用的导入,我会这样做。

有什么意见吗?

编辑(我觉得在函数中导入可能是个好主意):

主要原因:可以让代码更清晰。

查看函数代码时,我可能会问自己:“什么是函数/类 xxx?” (函数内部使用了xxx)。如果我在模块的顶部有我的所有导入,我必须去那里看看 xxx 是什么。这在使用from m import xxx 时更成问题。在函数中看到m.xxx 可能会告诉我更多信息。取决于m 是什么:它是众所周知的***模块/包(import m)吗?或者它是一个子模块/包(from a.b.c import m)? 在某些情况下,在使用 xxx 的位置附近添加额外信息(“什么是 xxx?”)可以使函数更易于理解。

【问题讨论】:

而你这样做是为了提高性能? 我觉得在某些情况下它使代码更清晰。我猜在导入函数时原始性能会下降(因为每次调用函数时都会执行导入语句)。 你可以回答“什么是函数/类xxx?”通过使用 import xyz 语法而不是 from xyz import abc 语法 如果清晰是唯一的因素,U 也可以包含相关的评论,以达到这个效果。 ;) @becomingGuru:当然可以,但是 cmets 可能与现实不同步... 【参考方案1】:

从长远来看,我认为您会喜欢将大部分导入放在文件顶部,这样您就可以通过需要导入的内容一眼看出您的模块有多复杂。

如果我要向现有文件添加新代码,我通常会在需要的地方进行导入,然后如果代码保持不变,我会将导入行移至文件顶部,从而使事情变得更加永久。

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

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

【讨论】:

感谢 ImportError 提示,我从来没有想过那个提示!【参考方案2】:

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

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

除了这两种情况之外,最好将所有内容都放在顶部。它使依赖关系更清晰。

【讨论】:

我同意它使整个模块的依赖关系更加清晰。但我相信,在函数级别导入所有内容会使代码不那么清晰。当您查看函数的代码时,您可能会问自己:“什么是函数/类 xxx?” (函数内部使用了xxx)。您必须查看文件的最顶部才能了解 xxx 的来源。这在使用 from m import xxx 时更成问题。看到 m.xxx 会告诉你更多信息——至少在对 m 是什么毫无疑问的情况下。【参考方案3】:

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

    import(以及from x import yimport 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 编写的数据结构。这有点像腌制的数据 除非我们是手工腌制的。

    这也或多或少位于模块的顶部


以下是我们为使代码更清晰所做的工作:

保持模块简短。

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

在某些情况下,在使用名称的位置附近提供这些额外信息可以使函数更易于理解。如果模块很短,那很容易做到。

【讨论】:

保持模块简短当然是个好主意。但是为了获得始终拥有可用功能的“导入信息”的好处,最大模块长度必须是一个屏幕(最多可能 100 行)。在大多数情况下,这可能太短而无法实用。 我想你可以把它带到一个合乎逻辑的极端。我认为可能存在一个平衡点,您的模块“足够小”以至于您不需要花哨的导入技术来管理复杂性。我们的平均模块大小——巧合的是——大约 100 行。【参考方案4】:

要记住一件事:不必要的导入可能会导致性能问题。所以如果这是一个经常被调用的函数,你最好把导入放在顶部。当然,这一种优化,所以如果有一个有效的案例表明在函数内部导入比在文件顶部导入更清晰,那么在大多数情况下这胜过性能。

如果您正在使用 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

【讨论】:

我同意您的维护问题。重构代码可能有点问题。如果我添加第二个函数,该函数使用以前仅由一个函数使用的模块 - 我要么将导入移到顶部,要么通过在第二个函数中导​​入模块来打破我自己的一般规则。 我认为性能参数也可以反过来。导入模块可能很耗时。在像超级计算机这样的分布式文件系统上,导入像 numpy 这样的大模块可能需要几秒钟的时间。如果一个模块只需要一个很少使用的函数,那么在函数中导入将显着加快常见情况。【参考方案5】:

对于自测模块,我之前已经违反了这条规则。也就是说,它们通常只用于支持,但我为它们定义了一个 main ,这样如果你自己运行它们,你就可以测试它们的功能。在那种情况下,我有时会在 main 中导入 getoptcmd,因为我希望阅读代码的人清楚这些模块与模块的正常操作无关,仅包含在测试中.

【讨论】:

【参考方案6】:

看看 sqlalchemy 中使用的替代方法:依赖注入:

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

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

这种方法使代码更简洁,并且比 import 语句快 4.5 倍

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

【讨论】:

【参考方案7】:

来自关于loading the module twice 的问题 - 为什么不两者兼而有之?

脚本顶部的导入将指示函数中的依赖关系和另一个导入,使该函数更具原子性,同时似乎不会导致任何性能劣势,因为连续导入很便宜。

【讨论】:

【参考方案8】:

还有另一种(可能是“角落”)情况,它可能对 import 内部很少使用的函数有益:缩短启动时间。

我曾经在一个小型物联网服务器上运行一个相当复杂的程序,从串行线路接受命令并执行操作,可能是非常复杂的操作。

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

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

【讨论】:

【参考方案9】:

只要是import 而不是from x import *,就应该将它们放在顶部。它只向全局命名空间添加一个名称,并且您坚持使用 PEP 8。另外,如果您以后在其他地方需要它,您不必移动任何东西。

这没什么大不了的,但由于几乎没有区别,我建议按照 PEP 8 所说的去做。

【讨论】:

实际上,将from x import * 放在一个函数中会产生一个 SyntaxWarning,至少在 2.5 中是这样。【参考方案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()

【讨论】:

以上是关于导入内部函数是pythonic吗?的主要内容,如果未能解决你的问题,请参考以下文章

Python 中的 int 对象可以在函数内部变得可迭代吗?

python学习笔记第八节(函数的相关作业)

函数内部的函数 - 每次?

Python 生成器 匿名函数 递归 模块及包的导入 正则re

python3闭包

PHP 如果我在函数内部使用 requre() 是调用函数之前的那个文件吗?