【问题标题】:In Python, what happens when you import inside of a function? [duplicate]在 Python 中,当您在函数内部导入时会发生什么? [复制]
【发布时间】:2011-03-06 22:07:50
【问题描述】:

在速度和内存效率方面,在函数内部导入 Python 模块和/或函数的优缺点是什么?

是否在每次运行函数时重新导入,或者无论函数是否运行,都可能只在开始时重新导入一次?

【问题讨论】:

  • 没有速度优势(调用import 非常昂贵,即使模块已经加载)。如果您想要速度优势,则将模块分配给局部变量作为您在函数中执行的第一件事,然后通过该局部变量访问它会更快(如果您访问模块至少 4-5 次) (因为局部变量查找非常快)。
  • @Nick:听这个的时候,重复导入似乎很慢,因为每次你都在尝试检查它是否被导入。你是说在函数外导入它并设置为全局变量并在函数内部抓取全局变量?
  • @Tim:加快模块访问速度的最佳方法(假设这是您想要做的,并且您访问模块足以使本地分配值得)是import模块像往常一样在文件级别,然后在函数内部将模块分配给局部变量。为了使分配有价值,您可能需要在函数内访问该模块至少 4 次 - 如果您使用该模块的频率低于此,则在全局级别进行直接 module.symbol 查找不会更慢比本地分配/查找。
  • @NickBastin 将模块分配给局部变量是否仍然是 5 年半后的优化?
  • @NickBastin 您的 pycon 链接已损坏。

标签: python python-import


【解决方案1】:

每次运行函数时都会重新导入吗?

没有;或者更确切地说,Python 模块在每次导入时基本上都会被缓存,因此导入第二次(或第三次或第四次……)实际上并不会强制它们再次经历整个导入过程。 1

无论函数是否运行,是否在开始时导入一次?

不,它仅在函数执行时才被导入。 2, 3

至于好处:这取决于,我猜。如果您可能只很少运行一个函数并且不需要在其他任何地方导入模块,那么可能只在该函数中导入它是有益的。或者,如果存在名称冲突或其他原因,您不希望模块中的模块或符号无处不在,您可能只想在特定函数中导入它。 (当然,对于这些情况,总是有from my_module import my_function as f。)

在一般实践中,它可能没有那么有益。事实上,大多数 Python 风格指南都鼓励程序员将所有导入放在模块文件的开头。

【讨论】:

  • 在同一思路下,如果导入隐藏在辅助函数中,这可以使依赖成为可选。
  • 我在为自己编写库模块时将它用于可选依赖项。我使库模块中的每个函数都依赖于最少数量的导入。
  • 谢谢!通过将慢速模块`import plotly`放在调用它的函数中,它节省了我的web2py应用程序的大量加载时间。
  • 我发现有时我需要 package A 来完成特定任务。后来我重写了代码并决定改用 package B 。那时,我可能不记得 package A 是否在我的项目中的其他地方使用过,并且可能在不再需要时不删除它,或者在仍然需要时删除它(我不会注意到如果它是代码中很少使用的部分,请立即使用)。我想知道当您不以python方式导入内部函数时,是否有其他人可以避免此类问题?
  • @PatrickT 如果您使用的是 IDE,他们往往会告诉您是否未访问导入。例如,在 VSCode 中,如果我没有访问导入,Pylance 会告诉我。
【解决方案2】:

您第一次从任何地方(函数内部或外部)import goo 时,goo.py(或其他可导入形式)被加载,sys.modules['goo'] 被设置为由此构建的模块对象。在程序的同一运行中(再次,无论是在函数内部还是外部)中的任何未来导入只需查找sys.modules['goo'] 并将其绑定到适当范围内的barename goo。 dict 查找和名称绑定是非常快速的操作。

假设第一个 import 在程序运行中完全摊销,具有“适当范围”是模块级意味着每次使用 goo.thisgoo.that 等,都是两个 dict 查找——一个goo 和一个属性名称。将其设置为“函数级别”会为每次运行函数支付一个额外的局部变量设置(甚至比字典查找部分更快!)但为每个 @ 保存一个 dict 查找(将其交换为局部变量查找,非常快) 987654330@ (etc) 访问,基本上将此类查找所需的时间减半。

我们以某种方式谈论几纳秒,因此这几乎不是值得优化的。在函数中使用import 的一个潜在的实质性优势是,在程序的给定运行中可能根本不需要该函数,例如,该函数通常处理错误、异常和罕见情况;如果是这种情况,任何不需要该功能的运行甚至都不会执行导入(这节省了微秒,而不仅仅是纳秒),只有确实需要该功能的运行才会支付(适度但可衡量的)价格。

这仍然是一种仅在非常极端的情况下才值得的优化,在尝试以这种方式挤出微秒之前,我会考虑许多其他优化。

【讨论】:

  • 这种优化实际上是不值得的 - 再多的快速局部变量访问也无法弥补调用import 的巨大开销,即使模块已经加载。检查模块是否已加载是一项非常昂贵的操作(相对于一些全局字典查找而言)。
  • 第一次导入一个模块很昂贵。尝试运行一个空脚本与仅包含 import string,itertools,fractions,heapq,re,array,bisect,collections,math,os 的脚本。第一个平均需要 180 毫秒,第二个平均需要 230 毫秒。所以对于初学者来说不是微秒。这是几十毫秒(也许发生了磁盘访问?)。这对于多次运行的小型脚本(比如服务网络请求)来说非常重要。
  • @EvgeniSergeev 在这种情况下,您通常有一个服务器一直在运行,因此它不会一遍又一遍地重新导入
  • @TobiasKienzler 它仍然适用于 FaaS(功能即服务)环境和/或一些低性能(例如嵌入式)设备。理由是延迟的差异可能很大,不能简单地忽略。
【解决方案3】:

函数第一次执行时导入一次。

优点:

  • 与其所用函数相关的导入
  • 易于在包中移动函数

缺点:

  • 无法查看此模块可能依赖于哪些模块

【讨论】:

  • 如果您执行grep import /path/to/module 之类的操作,它将向您显示它导入的所有模块。
【解决方案4】:

我是否可以笼统地建议不要问“X 会提高我的表现吗?”您使用分析来确定您的程序实际在哪里花费时间,然后根据您将获得最大收益的地方应用优化?

然后您可以使用分析来确保您的优化实际上也使您受益。

【讨论】:

  • 我同意这一点,这更像是一个好奇的问题。我想知道 Python 的导入方法如何更详细地工作,而不是尝试过早地增强性能。不过谢谢:)
  • 啊。好了,希望这里的优秀答案满足了你的好奇心! Effbot 有一些可能对您有用的信息:effbot.org/zone/import-confusion.htm 向下滚动到“Python 做什么来导入模块?”
  • 感谢您提供的信息,答案非常好并且很有帮助。
【解决方案5】:

在函数内部导入将有效地导入模块一次.. 在函数第一次运行时。

无论是在顶部导入还是在函数运行时导入,它都应该以同样快的速度导入。这通常不是导入 def 的好理由。优点?如果不调用函数就不会导入。。如果你的模块只要求用户安装某个模块,如果他们使用你的特定功能,这实际上是一个合理的理由......

如果这不是你这样做的原因,那几乎可以肯定这是一个令人讨厌的想法。

【讨论】:

    【解决方案6】:

    第一次调用函数时导入一次。

    如果我在导入的模块中有一个很少使用并且是唯一需要导入的函数,我可以想象这样做。不过看起来有点牵强……

    【讨论】:

      猜你喜欢
      • 2017-07-12
      • 2020-03-04
      • 1970-01-01
      • 2019-03-31
      • 2016-12-02
      • 1970-01-01
      • 2018-07-15
      • 2012-07-30
      相关资源
      最近更新 更多