导入函数内部是pythonic吗?


126

PEP 8说:

  • 导入总是放在文件的顶部,紧随任何模块注释和文档字符串之后,以及模块全局变量和常量之前。

占用时,我违反了PEP8。有时,我在函数中导入了东西。通常,如果存在仅在单个函数中使用的导入,则执行此操作。

有什么意见吗?

编辑(我觉得导入函数的原因可能是个好主意):

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

  • 在查看函数代码时,我可能会问自己:“函数/类xxx是什么?” (在函数内部使用xxx)。如果我的所有导入都在模块顶部,则必须去那里确定xxx是什么。使用时,这更成问题from m import xxx。看到m.xxx该功能可能会告诉我更多信息。取决于什么m:它是众所周知的顶级模块/软件包(import m)?还是子模块/包(from a.b.c import m)?
  • 在某些情况下,具有与使用xxx接近的位置的额外信息(“ xxx是什么?”)可以使功能更易于理解。

2
你这样做是为了表现?
Macarse

4
我觉得在某些情况下它使代码更清晰。我猜想在导入函数时原始性能会下降(因为每次调用该函数都会执行import语句)。
codeape

您可以回答“什么是函数/类xxx?” 通过使用import xyz语法而不是from xyz import abc语法
Tom Leys

1
如果只有清晰度是唯一因素,那么U也可以为此添加相关注释。;)
Lakshman Prasad 2009年

5
@becomingGuru:当然可以,但是评论可能与现实不同步...
Codeape

Answers:


88

从长远来看,我想您会喜欢将大多数导入都放在文件顶部的方式,这样一来您就可以一目了然地判断模块需要导入的内容有多复杂。

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

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

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


47

在这方面,我有两次违反PEP 8的情况:

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

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


7
我同意这样可以使整个模块的依赖关系更加清晰。但我相信,这样做会使代码在功能级别上不那么清楚,从而无法在顶部导入所有内容。当您查看函数的代码时,您可能会问自己:“函数/类xxx是什么?” (在函数内部使用xxx)。而且您必须查看文件的最顶部以了解xxx的来源。当从m import xxx使用时,这更成问题。看到m.xxx可以告诉您更多信息-至少对于m是什么毫无疑问。
codeape

20

这是我们使用的四个导入用例

  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. 有条件的导入。与JSON,XML库等一起使用。在顶部。

    try:
        import this as foo
    except ImportError:
        import that as foo
  4. 动态导入。到目前为止,我们只有一个例子。

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

    请注意,这种动态导入不会引入代码,而是引入以Python编写的复杂数据结构。这有点像腌制的数据,只是我们手工腌制了。

    这或多或少都在模块的顶部


这是使代码更清晰的方法:

  • 保持模块简短。

  • 如果我将所有导入内容都放在模块顶部,则必须去那里确定名称。如果模块很短,那很容易做到。

  • 在某些情况下,使多余的信息靠近名称的使用位置可使该功能更易于理解。如果模块很短,那很容易做到。


使模块简短当然是一个非常好的主意。但是要获得始终为可用功能提供“导入信息”的好处,最大模块长度必须为一个屏幕(最多100行)。在大多数情况下,这可能太短而无法实际应用。
codeape

我想您可以将此推到逻辑上的极限。我认为可能存在一个平衡点,您的模块“足够小”,您不需要花哨的导入技术来管理复杂性。巧合的是,我们的平均模块大小约为100行。
S.Lott

8

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

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

2
我同意您的维修问题。重构代码可能会有些问题。如果添加第二个功能,该功能使用以前仅由一个功能使用的模块-我要么将导入移动到顶部,要么通过将模块也导入第二个功能来破坏自己的一般规则。
codeape

2
我认为性能方面的争论也可以相反。导入模块可能很耗时。在像超级计算机上那样的分布式文件系统上,导入像numpy这样的大模块可能要花费几秒钟。如果只有一个很少使用的功能只需要一个模块,那么导入该功能将大大加快常见情况的速度。
amaurea

6

对于自测模块,我之前已经打破了此规则。也就是说,它们通常仅用于支持,但是我为它们定义了一个主要版本,因此,如果您自己运行它们,则可以测试其功能。在那种情况下,我有时会导入getoptcmd只是进入main,因为我希望阅读代码的人可以清楚地知道这些模块与模块的正常运行无关,仅包含在测试中。


5

来自关于 两次加载模块 -为什么不两者都?

脚本顶部的导入将指示依赖关系,并且该函数中的另一个导入将使该函数更具原子性,同时由于连续导入的成本较低,因此似乎不会造成任何性能劣势。


3

只要它importfrom x import *,您就应该将它们放在顶部。它仅向全局名称空间添加一个名称,并且您坚持使用PEP8。此外,如果以后需要在其他地方使用它,则无需四处移动。

没什么大不了的,但是由于几乎没有区别,所以我建议按照PEP 8的说明进行操作。


3
实际上,from x import *至少在2.5中,将函数放入函数中将生成SyntaxWarning。
里克·科普兰

3

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

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

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

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

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


2

在既是“正常”模块又可以执行的模块中(即 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()

1

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

我曾经在小型IoT服务器上运行一个相当复杂的程序来碰壁,它接受来自串行线路的命令并执行操作,可能是非常复杂的操作。

import语句放在文件顶部意味着在服务器启动之前已处理所有导入;因为import名单中包括jinja2lxmlsignxml等“重物”(和SoC是不是很厉害),这意味着分钟的第一个指令之前实际执行。

OTOH将大多数导入放置在功能中,我能够在几秒钟内使服务器在串行线上“运行”。当然,当实际需要模块时,我必须付出代价(注意:这也可以通过import在空闲时间内生成后台任务来缓解)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.