【问题标题】:Why can't Python's import work like C's #include?为什么 Python 的导入不能像 C 的 #include 那样工作?
【发布时间】:2020-08-24 11:47:31
【问题描述】:

大约一年来,我一直在努力理解 Python 的导入,但我几乎放弃了 Python 编程,因为它看起来太模糊了。我来自 C 背景,我认为 import 的工作方式类似于 #include,但如果我尝试导入某些内容,我总是会出错。

如果我有两个这样的文件:

foo.py:

a = 1

bar.py:

import foo
print foo.a
input()

为什么我需要引用模块名称?为什么不能写import fooprint a?这种混乱的意义何在?为什么不直接运行代码并为您定义一些东西,就好像您将它写在一个大文件中一样?为什么它不能像 C 的 #include 指令那样工作,它基本上是复制和粘贴你的代码?我在 C 中没有导入问题。

【问题讨论】:

标签: python c import


【解决方案1】:

做你想做的事,你可以使用(不推荐,进一步阅读解释):

from foo import *

这会将所有内容导入您当前的命名空间,您将能够调用print a

但是,这种方法的问题如下。考虑有两个模块moduleAmoduleB 的情况,每个模块都有一个名为GetSomeValue() 的函数。 当你这样做时:

from moduleA import *
from moduleB import *

您遇到了命名空间解析问题*,因为您实际使用 GetSomeValue()moduleA.GetSomeValue()moduleB.GetSomeValue() 调用了什么函数?

除此之外,您还可以使用Import As 功能:

from moduleA import GetSomeValue as AGetSomeValue
from moduleB import GetSomeValue as BGetSomeValue

或者

import moduleA.GetSomeValue as AGetSomeValue
import moduleB.GetSomeValue as BGetSomeValue

此方法手动解决冲突。

我相信您可以从这些示例中体会到显式引用的必要性。

* Python有自己的命名空间解析机制,这里只是为了解释的简化。

【讨论】:

  • 这不是坏习惯吗?
  • @turbulencetoo: 或者你可以明确地导入名称。 from foo import a.
  • @DylanLaCoursiere:您错误地假设您是所有代码的唯一作者。但是不同的程序员在不同的模块上工作,许多第三方模块可以在你的应用程序中使用。您不能确保它们都具有唯一的函数和变量名称。这很愚蠢,如果你仔细想想,命名空间确实是最好的解决方案。
  • @DylanLaCoursiere:我不认为你理解这个问题。看看下面的案例。一个人正在开发一个 TCP 客户端库。它显然会有 Connect() 方法,因为这是 TCP 客户端所做的。与此同时,在世界的另一端,有人正在开发 HTTP 客户端。很明显他会为它实现 Connect 函数。几天后,有人决定创建一个需要 TCP 和 HTTP 客户端功能的应用程序,但不想开发它。他想使用第三方模块,他包括了前面的两个......
  • @DylanLaCoursiere:只是为了添加关于命名空间的另一点。我猜您的计算机上有一些名称相同但位于不同目录的文件。为什么不将它们全部放在同一个目录中,而是为它们添加前缀?因为那不是做事的自然方式。目录是命名空间,它们允许多个同名文件共存,只要它们位于不同的文件夹中。
【解决方案2】:

假设你的模块中有一个函数,它从列表中选择一些对象:

def choice(somelist):
    ...

现在进一步想象,无论是在该函数中还是在模块的其他地方,您都在使用来自random 库的randint

a = randint(1, x)

所以我们

import random

您的建议,这就是 from random import * 现在可以访问的,这意味着我们现在有 两个不同的函数,称为选择,因为 random 也包括一个。只有一个是可访问的,但您对 choice() 在代码中其他地方的实际引用引入了歧义。

这就是为什么导入所有内容是不好的做法;要么导入你需要的东西:

from random import randint
...
a = randint(1, x)

或整个模块:

import random
...
a = random.randint(1, x)

这有两个好处:

  1. 您将名称重叠的风险降至最低(现在和将来添加到您的导入模块中);和
  2. 当其他人阅读您的代码时,他们可以轻松查看外部函数的来源。

【讨论】:

  • 我们只有一个名为 choice 的函数,无论以后出现什么。
  • @J.F.Sebastian 是的,没错,我的意思是 choice 所指的内容有歧义 - 我会更清楚地说明这一点
  • 或者您可以更改函数的名称,以免发生名称冲突。再一次,非常简单的事情,被混淆以防止搞砸。一盎司的预防就是一磅的治疗。如果您认为会发生命名冲突,请将函数名称更改为其他名称。如果您在使用 choice() 时遇到问题,请将其更改为 choice_of() 或类似的东西。我认为这里不应该有这么多,只需运行文件并为您定义名称即可。
  • @DylanLaCoursiere:您是否控制、阅读并记住您在所有系统上导入的所有模块的所有版本的源代码?
  • @DylanLaCoursiere:您需要 100% 的测试代码覆盖率,并且测试应该足够彻底以触发行为差异具有相同名称的函数可能具有相似的功能。 Python 是一种动态语言。无论如何,你的代码不应该因为你不使用的东西而中断。如果您使用模块中的单个函数,则不应关心该模块中今天具有的其他函数以及将在未来版本中引入的其他函数。
【解决方案3】:

有几个很好的理由。该模块为其中的对象提供了一种命名空间,它允许您使用简单的名称而不必担心冲突——来自 C 背景,您肯定见过带有长而丑陋的函数名称的库,以避免与其他任何人发生冲突。

此外,模块本身也是对象。当一个模块在 python 程序中的多个地方被导入时,每个地方实际上都得到了相同的引用。这样,更改foo.a 会为每个人更改它,而不仅仅是本地模块。这与 C 形成对比,其中包含标头基本上是复制+粘贴到源文件中的操作(显然您仍然可以共享变量,但机制有点不同)。

如前所述,您可以说from foo import * 或更好的from foo import a,但要了解底层行为实际上是不同的,因为您将a 绑定到本地模块。

如果你经常使用一些东西,你总是可以使用from语法直接导入它,或者你可以将模块重命名为更短的东西,例如

import itertools as it

【讨论】:

    【解决方案4】:

    当您执行import foo 时,会在名为foo 的当前命名空间内创建一个新的module

    所以,在 foo 中使用任何东西;你必须通过模块来解决它。

    但是,如果您使用 from from foo import something,则不必在模块名称前面加上 use,因为它将从模块中加载 something 并为其分配名称 something。 (不推荐的做法)

    【讨论】:

      【解决方案5】:
      import importlib
      
      # works like C's #include, you always call it with include(<path>, __name__)
      def include(file, module_name):
          spec = importlib.util.spec_from_file_location(module_name, file)
          mod = importlib.util.module_from_spec(spec)
          # spec.loader.exec_module(mod)
          o = spec.loader.get_code(module_name)
          exec(o, globals())
      

      例如:

      #### file a.py ####
      a = 1
      
      #### file b.py ####
      b = 2
      
      if __name__ == "__main__":
          print("Hi, this is b.py")
      
      #### file main.py ####
      # assuming you have `include` in scope 
      include("a.py", __name__)
      print(a)
      include("b.py", __name__)
      print(b)
      

      输出将是:

      1
      Hi, this is b.py
      2
      

      【讨论】:

        猜你喜欢
        • 2011-05-13
        • 2016-05-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-05-12
        • 1970-01-01
        相关资源
        最近更新 更多