require与import加载代码的优缺点是什么?

9 投票
4 回答
6886 浏览
提问于 2025-04-15 16:42

Ruby语言使用的是require,而Python语言使用的是import。这两者在工作原理上有很大的不同。虽然我更习惯使用require,但我发现有些地方我更喜欢import的方式。我很好奇大家觉得在这两种方式中,哪些地方特别简单,或者更有趣的是,哪些地方其实比想象中要难。

特别是,如果你要设计一种新的编程语言,你会怎么设计代码加载的机制呢?在你的设计选择中,哪些优点和缺点会对你影响最大呢?

4 个回答

1

声明一下,我并不是Python方面的专家。

我觉得require相比import最大的好处就是,你不需要担心命名空间和文件路径之间的关系。很简单:它就是一个标准的文件路径。

我很喜欢import对命名空间的强调,但我不禁想,这种方法是不是太死板了。根据我的理解,在Python中,控制模块名称的唯一方法就是修改被导入模块的文件名,或者使用as来重命名。此外,使用明确的命名空间时,你可以通过完整的标识符来引用某个东西,但在隐式命名空间中,你在模块内部无法做到这一点,这可能会导致一些模糊的情况,而这些情况很难通过重命名来解决。

也就是说,在foo.py中:

class Bar:
  def myself(self):
    return foo.Bar

这会失败,错误信息是:

Traceback (most recent call last):
  File "", line 1, in ?
  File "foo.py", line 3, in myself
    return foo.Bar
NameError: global name 'foo' is not defined

这两种实现都使用了一个位置列表来进行搜索,我觉得这是一个非常重要的部分,无论你选择哪种模型。

如果使用像require这样的代码加载机制,但语言本身没有全局命名空间会怎么样?也就是说,所有东西都必须有命名空间,但开发者可以完全控制类定义在哪个命名空间中,而这种命名空间的声明是在代码中明确写出的,而不是通过文件名来决定。或者,定义在全局命名空间中的东西会产生警告。这是一种两全其美的方法,还是说我遗漏了什么明显的缺点?

4

一个很不错的特点是,require其实是定义在Kernel里的一个方法。所以你可以重写它,自己实现一个Ruby的包管理系统,这就是Rubygems所做的事情!

PS:我并不是在推销“猴子补丁”,而是想说Ruby的包管理系统是可以被用户重写的(甚至可以做到像Python的系统那样)。当你在写一种新的编程语言时,不可能所有的地方都做到完美。因此,如果你的导入机制可以在语言内部完全扩展(向各个方向扩展),那对未来的用户来说是最好的服务。如果一种语言不能在内部完全扩展,那就是一种进化的死胡同。我觉得这是Matz在Ruby上做对的事情之一。

17

在Python中,import有一个很重要的特点,就是它把两件事情联系在一起——如何找到要导入的内容在什么命名空间下使用它

这让代码变得非常明确:

import xml.sax

这行代码告诉我们要使用的代码在哪里,可以通过Python的搜索路径规则找到它。

同时,我们想要访问的所有对象都在这个特定的命名空间下,比如xml.sax.ContentHandler

我觉得这比Ruby的require要好。因为require 'xml'可能会让XML命名空间下的对象或者其他命名空间的对象在模块中可用,但这并不直接从require那一行看出来。

如果xml.sax.ContentHandler太长了,你可以在导入时指定一个不同的名字:

import xml.sax as X

这样它就可以用X.ContentHandler来访问了。

通过这种方式,Python要求你明确地构建每个模块的命名空间。因此,Python的命名空间是非常“物理”的,接下来我来解释一下这是什么意思:

  • 默认情况下,只有在模块中直接定义的名字可以在它的命名空间中使用:比如函数、类等等。
  • 要想往模块的命名空间中添加内容,你需要明确地导入你想添加的名字,把它们(通过引用)“物理上”放在当前模块中。

举个例子,如果我们有一个小的Python包“process”,里面有子模块machineinterface,我们希望把它们作为一个方便的命名空间直接放在包名下,这就是我们可以在“包定义”文件process/__init__.py中写的内容:

from process.interface import *
from process.machine import Machine, HelperMachine

这样我们就把通常可以通过process.machine.Machine访问的内容提升到了process.Machine。同时,我们也以非常明确的方式把process.interface中的所有名字添加到process的命名空间中。

我提到的Python的import的两个优点就是:

  • 清晰:使用import时你包含了什么
  • 明确:你是如何修改自己模块的命名空间的(无论是为了程序本身还是为了其他人导入)

撰写回答