Python:为什么禁止“from <module> import *”?
- 2025-04-10 09:47:00
- admin 原创
- 23
问题描述:
如果你碰巧有
from <module> import *
在程序(或模块)的中间,你会收到警告:
/tmp/foo:100: SyntaxWarning: import * only allowed at module level
我理解为什么import *
一般不鼓励这样做(命名空间不可见性),但在很多情况下它会很方便,特别是在代码不与任何人共享的情况下。
那么,有人可以详细解释为什么from <module> import *
在所有可能的情况下都应该禁止吗?
解决方案 1:
我相信“在你的程序中间”你正在谈论函数定义内部的导入:
def f():
from module import * # not allowed
这是不允许的,因为这会使优化函数体变得太难。Python 实现希望在对函数进行字节编译时知道所有函数局部变量的名称,以便它可以将变量引用优化为 (CPython) 虚拟机操作数堆栈上的操作,或者至少优化为局部变量槽操作,而不是在外部命名空间中查找。如果您可以将模块的全部内容转储到函数的本地命名空间中,那么编译器必须假定函数中的任何名称都可能引用模块全局变量,因为引入的名称列表from module import *
仅在运行时才为人所知。
在顶级声明from module import *
之间插入是糟糕的风格,但是这是允许的:
def f():
...
from module import *
def g():
...
编辑于 2013 年 4 月:在研究其他内容时,我发现此限制是在 Python 2.1 中引入的,是“嵌套范围”功能( PEP 227 )的结果。引用自链接:
这一变化的一个副作用是,在某些情况下,
from module import *
和exec
语句在函数作用域内变为非法。Python 参考手册一直说这只from module import *
在模块的顶层合法,但 CPython 解释器以前从未强制执行过这一点。作为嵌套作用域实现的一部分,将 Python 源代码转换为字节码的编译器必须生成不同的代码来访问包含作用域中的变量。from module import *
并使exec
编译器无法弄清楚这一点,因为它们在编译时无法知道的本地命名空间中添加了名称。因此,如果函数包含lambda
具有自由变量的函数定义或表达式,编译器将通过引发异常来标记这一点SyntaxError
。
这澄清了评论中讨论的 Python 3.x 与 2.x 行为。它始终违反语言规范,但 CPython 2.1 到 2.7 仅from module import *
在它可能影响编译器判断变量是本地绑定还是包含范围绑定的能力时,才会在函数内发出错误。在 3.x 中,它已升级为无条件错误。
编辑之子: ... 显然 flashk 几年前在另一个回答中指出了这一点,并引用了“Python 2.1 中的新增功能”中的同一段。现在大家去投票吧。
解决方案 2:
Python 2.1 的发行说明似乎解释了为什么存在这个限制:
这一变化的一个副作用是,在某些情况下,from module import 和 exec 语句在函数作用域内被变为非法。Python 参考手册一直说 from module import 只在模块的顶层合法,但 CPython 解释器以前从未强制执行过这一点。作为嵌套作用域实现的一部分,将 Python 源代码转换为字节码的编译器必须生成不同的代码来访问包含作用域中的变量。from module import * 和 exec 使编译器无法弄清楚这一点,因为它们在本地命名空间中添加了编译时无法知道的名称。因此,如果函数包含具有自由变量的函数定义或 lambda 表达式,编译器将通过引发 SyntaxError 异常来标记这一点。
解决方案 3:
在任何词汇层面上,from amodule import *
这都是一个“当时看似是个好主意”的设计决策,但在现实生活中却被证明是一场真正的灾难,除了在交互式解释器提示下进行方便的探索之外(即使在那时,我也不太喜欢它——import module as m
强制只有两个额外的字符使用限定名称而不是[[只是一个m.
前缀]],并且限定名称总是比裸名更清晰、更灵活,更不用说在探索性交互情况下有m
、help(m)
等reload(m)
可用的巨大用处!)。
这种混乱的结构让阅读代码的可怜人(通常是为了帮助调试而徒劳无功)很难理解这些神秘名称的来源——如果这种结构在词汇层面上使用多次,这是不可能的;但即使只使用一次,每次也不得不费力地重读整个模块,然后才能说服自己,是的,那个混乱的光秃秃的名字一定来自这个模块。
此外,模块作者通常不会费尽心思去“支持”有问题的可怕构造。如果您的代码中某个地方有一个使用sys.argv
(import sys
当然,在模块的最顶部),您怎么知道它sys
应该是哪个模块……或者来自的某个完全不同的模块(或非模块) ?!将它乘以您使用的所有合格名称,唯一的最终结果就是痛苦——那就是神秘的错误,需要长时间、费力的调试(通常是在不情愿的情况下,在“了解... import *
”Python的人的帮助下……!-)。
在函数中,添加和覆盖任意本地名称的方法会更糟糕。作为一项基本但关键的优化,Python 编译器会在函数主体中查找每个 barename 上的任何赋值或其他绑定语句,并将它看到的已赋值的名称视为“本地”(其他名称必须是全局或内置的)。使用 an import *
(就像使用 an 而不使用显式字典作为命名空间一样exec somestring
),突然间,哪些名称是本地的,哪些名称是全局的就完全成了一个谜——因此,可怜的编译器不得不对每次名称查找都采取最慢的策略,使用字典来表示局部变量(而不是它通常使用的紧凑“向量”),并一遍又一遍地对引用的每个 barename 执行最多三次字典查找。
转到任何 Python 交互式提示。输入import this
。你看到了什么?Python 之禅。该文本中最后一个也是最伟大的智慧是什么......?
命名空间是一个非常好的主意——让我们多做一些这样的事!
通过在限定名称非常可取的地方强制使用裸名,您实际上是在做与这一明智建议完全相反的事情:您不是欣赏命名空间的伟大和优雅,也不是更多地这样做,而是破坏了两个完好无损且可立即使用的命名空间(您正在导入的模块的命名空间和您正在导入它的词法范围的命名空间),从而形成一个邪恶、有缺陷、缓慢、僵化、无法使用的混乱局面。
如果我可以回到过去并改变Python 的一个早期设计决策(这是一个艰难的选择,因为使用def
和,尤其是lambda
对于可读性更强的 JavaScript 调用来说,function
是紧随其后的;-),我会追溯性地从 Guido 的脑海中抹去这个想法。在交互式提示符下进行探索的所谓import *
便利性再多也无法抵消它造成的邪恶程度……!-)
解决方案 4:
这并没有被禁止,因为......
...它对于快速脚本和 shell 探索非常方便。
...但你不应该将其保存在任何严肃的代码中
它可能会导致引入你不了解的名称并抹去本地名称
你无法知道代码中使用了什么,很难知道脚本的依赖关系
代码补全将不再正常工作
IDE 中诸如“此变量尚未声明”之类的便捷检查不再起作用
使创建循环导入更加容易
解决方案 5:
它根本没有被禁止。它工作正常,但你会收到警告,因为这通常是一个坏主意(出于其他人已经讨论过的原因)。如果你愿意,你可以抑制警告;警告模块就是你想要的。
解决方案 6:
其他人已经给出了深入的答案,我将简要概述我的理解。当使用 from 时,您可以直接调用您导入的模块中的任何函数,而无需执行 modulename.functioname(您可以直接调用“functionname”)如果您在不同的模块中有两个同名的函数,这会产生问题,并且在处理大量函数时也会造成混乱,因为您不知道它属于哪个对象/模块(从不熟悉它的已经编写的代码的人的角度来看)
扫码咨询,免费领取项目管理大礼包!