【问题标题】:What is the difference between import X and from X import *import X 和 from X import * 有什么区别
【发布时间】:2020-07-23 22:51:48
【问题描述】:

给出的常见解释(如12270954710551)是from X import * 使命名空间混乱,因此不推荐使用。但是,下面的例子 表明它不仅仅是混乱的命名空间。

考虑以下代码:

x1.py:

g_c = 5
class TestClass():
    def run(self):
        global g_c
        g_c = 1
        print(g_c) # prints 1

x2.py:

from x1 import *
t = TestClass()
t.run()
print(g_c) # prints 5, why?

x3.py:

import x1
t = x1.TestClass()
t.run()
print(x1.g_c) # prints 1

运行x2.pyx3.py 的结果不同(在python 3.6.8 上)。有人可以解释为什么 两个导入的行为不同?

额外说明:(展示@tdelaney 提到的关于列表的说明)

像这样更改 x1.py 中的分配:

g_c = [5]
...
g_c.append(1)

现在,x2.py 和 x3.py 都给出了相同的答案。只有在使用原子类型时才会出现问题。

【问题讨论】:

  • 因为星号导入分配给导入模块中的新变量。他们做两件不同的事情,他们的行为并没有真正不同。您可以将加星标的导入和from module import some_var 视为import module; some_var = module.some_var; del module 的糖
  • 对我来说,避免使用from X import * 的原因是为了将来的可读性。当我看到像 find 这样命名相当模糊的函数时,我想知道它是从哪里来的,而不需要通过导入。
  • 感谢@juanpa.arrivillaga,主要问题是对于“import *”,变量被复制而不被引用。这在 python 文档的任何地方都明确提到了吗?
  • @R71 这就是始终变量的工作方式。注意,object 没有被复制,新变量被添加到命名空间。如果没有特殊的赋值语义,它就无法以任何其他方式工作。

标签: python python-3.x


【解决方案1】:

当你import X时,你将不得不使用X来调用X内部的函数,即:

X.func1()

但是当您使用from X import * 时,您导入了模块命名空间中所有不以_ 开头的名称或module.__all__ 中的所有名称,并且您不需要使用X 来调用函数,即:

func1()

x2.py 你这样做的时候:

from x1 import *

您刚刚从 x1 导入了所有内容,即,包括变量。这意味着您更改了当前脚本的命名空间 (x2)。所以当你打电话时:

print(g_c)

打印出来:

5

从当前脚本命名空间调用。
x2 中的 TestClass() 类运行时,它更改了 x1 中 g_c 的值,但在 x2 中没有。

这表明使用全局变量的缺点。
当变量声明为全局变量时,它们将保留在内存中,直到程序执行完成。也就是说,您的全局声明仅在x1 内有效。该声明在x1 的命名空间内有效。


当你这样做时:

import x1
t = x1.TestClass()
t.run()

这个 import x1 作为一个模块,里面的所有东西都不会被导入,除非你调用它。你确实调用了类TestClass(),运行了它,导致类内部的g_c 被设为全局变量。那么对于x1g_c1,因为它是全局定义的,并且当前脚本不知道x1 有一个g_c = 5,因为它没有被导入/调用。
print(x1.g_c) 上,它调用x1 命名空间中g_c 的值,即1


【讨论】:

  • "你已经导入了 X 中的所有可调用对象" 不,你导入了模块命名空间中不以_ 开头的所有名称,或者module.__all__ 中的所有名称
  • 一个很大的答案,但它没有回答主要问题,为什么 x2 为 g_c 打印不同的值。此外,给出的答案是不正确的,并且在编写之前没有经过验证 - “我相信,如果你给出 x2:print(x1.g_c) 它会打印 1”。实际上它给出了一个错误,因为我已经完成了“从 x1 导入” *"。
  • 是的,这是一个错误。我会删除它。这是你说的错误部分吗?原因说明
  • 是的,那是错误的部分。但无论如何,一个很长的答案会分散所提出的主要问题的注意力,@6502 简要回答了这个问题。
【解决方案2】:

让我们举个例子,在 Python 中导入模块 suman。它将整个模块带入您的工作区,即在import suman 语句中,它不会让您访问与like 关联的任何方法或函数,例如,如果它们是suman 模块中的函数display_name(),那么您必须像导入它一样suman.display_name()

from suman import * 中,它将suman 模块中的所有名称(例如display())带入您的模块中。现在您可以访问这些名称而无需模块名称的前缀,如下所示:

from suman import *
print(display_name())

【讨论】:

    【解决方案3】:

    from X import * 将 X 的全局命名空间中的对象重新绑定到当前模块的命名空间。如果这些变量之一被重新分配,则重新分配对当前命名空间是私有的。

    导入不会更改导入的对象,但它只是添加了一个新引用。例如,class TestClass 的全局命名空间仍然是 x1.py 模块。 TestClass.run 总是改变 x1.g_c它的 全局命名空间),无论是哪个模块调用它。

    在 x2.py 中,您将 x1.g_c 引用的原始 5 导入到 x2.g_c。但是重新分配x1.g_c 不会影响x2.g_c,它是不同命名空间中的变量。

    相反,假设x1.g_c 是一个列表,而您的代码正在附加到该列表中。在这种情况下,所有模块都会看到附加,因为您正在修改 x1.g_c 引用的对象,而不是重新分配它。

    【讨论】:

    • 谢谢,特别是提到列表不会显示差异。
    【解决方案4】:

    你可以想象from x1 import *做的事情大约等于

    import m1
    globals.update(m1.__dict__)
    del m1
    

    换句话说,它会导入模块,然后将所有全局变量复制到您的命名空间中。然而,模块内的函数和类将继续引用它们的原始全局变量,而不是命名空间中的副本。

    请注意,上面的“实现”并不完全正确,因为并非所有名称都被复制并且还有其他细微之处,但我认为这是理解一般行为的简单方法。

    当模块更改其全局变量但您看不到全局命名空间中反映的更改时,差异变得明显。

    执行from x1 import *后,变量sys.modules["x1"].g_cg_c是两个不同的变量,具有相同的值;模块中的代码是指模块内部的代码,而不是您的副本。

    【讨论】:

    • 我在你的回答中突出显示了“副本”这个词,我认为这是理解这个问题的关键词。但是你能从python手册中给出一个明确提到这一点的参考吗,我找不到它。
    • @R71:使用的技术术语是 Python 文档是“绑定”;在docs.python.org/3/reference/… 中,from ... import * 被解释为为所有名称创建绑定(以_ 开头的除外)。这是制作“副本”的地方。
    • 是的,重要的是要指出,“绑定”只是正常的赋值,就像a = 42; b = a; b = 0; print(a) prints 42 not 0
    【解决方案5】:

    此行为的关键如下:

    1. Python 为每个文件创建命名空间
    2. global X 使变量 X 指向位于声明 global X 的命名空间中的对象

    t.run() 在 x2.py 中执行时,global g_c 会被计算,这使得g_c 指的是 x1.py 而不是 x2.py 的命名空间中的对象。

    尝试以下 sn-p 会有所帮助。

    x2_.py

    from x1 import *
    t = TestClass()
    t.run()
    print(g_c)  # prints 5
    
    import x1
    print(x1.g_c) # this print 1
    

    【讨论】:

      猜你喜欢
      • 2017-09-18
      • 2019-07-07
      • 2019-06-07
      • 1970-01-01
      • 2011-07-04
      • 2013-05-20
      • 2015-08-24
      • 1970-01-01
      • 2018-09-21
      相关资源
      最近更新 更多