加载代码的require和import方法有哪些优点和缺点?

时间:2009-12-04 20:13:00

标签: python ruby programming-languages language-features language-design

Ruby使用require,Python使用import。它们是完全不同的模型,虽然我更习惯于require模型,但我可以看到一些我认为更喜欢import的地方。我很好奇人们发现这些模型中的每一个都特别容易 - 或者更有趣,更难以应对 -

特别是,如果您正在编写新的编程语言,您将如何设计代码加载机制?哪种“专业”和“缺点”会对您的设计选择产生最大影响?

4 个答案:

答案 0 :(得分:14)

Python import有一个主要特点,它将两个东西联系在一起 - 如何找到导入,并在下包含它的名称空间

这会创建非常明确的代码:

import xml.sax

这指定了根据Python搜索路径的规则在哪里找到我们想要使用的代码。

同时,我们要访问的所有对象都存在于此确切的命名空间下,例如xml.sax.ContentHandler

我认为这是Ruby的要求的优势。 require 'xml'实际上可能会在命名空间XML中创建对象,或者在模块中提供任何其他命名空间,而这一点在需求行中无法直接显示。

如果xml.sax.ContentHandler太长,您可以在导入时指定其他名称:

import xml.sax as X

现在它在X.ContentHandler下有效。

这种方式Python要求您显式构建每个模块的命名空间。因此,Python命名空间非常“物理”,我将解释我的意思:

  • 默认情况下,只有模块中直接定义的名称才能在其名称空间中使用:函数,类等。
  • 要添加到模块的命名空间,请明确导入要添加的名称,将它们(通过引用)“物理”放置在当前模块中。

例如,如果我们有一个包含内部子模块machineinterface的小型Python包“process”,我们希望将它作为一个方便的命名空间直接显示在包名下,这是和我们可以在“包定义”文件process/__init__.py中编写的示例:

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

因此,我们将process.machine.Machine通常可以访问的内容提升到process.Machine。我们以非常明确的方式将所有名称从process.interface添加到process命名空间。

我写的Python导入的优点只有两个:

  • 清除使用import
  • 时包含的内容
  • 明确如何修改自己模块的命名空间(用于程序或其他人导入)

答案 1 :(得分:3)

require的一个不错的属性是它实际上是Kernel中定义的方法。因此,您可以覆盖它并为Ruby实现自己的包装系统,例如, Rubygems呢!

PS:我不是在这里销售猴子补丁,但事实上Ruby的包系统可以被用户重写(甚至像python的系统那样工作)。当你编写一种新的编程语言时,你无法把一切都搞定。因此,如果您的导入机制完全可以从语言中扩展(完全向所有方向),那么您将为未来的用户提供最好的服务。从内部无法完全扩展的语言是一个进化的死胡同。我想这是Matz用Ruby做的事情之一。

答案 2 :(得分: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这样的代码加载机制,但语言根本没有全局命名空间怎么办?即,所有地方,每个地方都必须是命名空间,但开发人员可以完全控制定义类的命名空间,并且该命名空间声明在代码中显式发生,而不是通过文件名。或者,在全局命名空间中定义某些内容会生成警告。这是一个两全其美的方法,还是有一个明显的缺点,我错过了?

答案 3 :(得分:1)

Python的import提供了一种非常明确的命名空间:命名空间是路径,您不必查看文件以了解它们在其定义中使用的命名空间,并且您的文件不会被命名空间定义混乱。这使得应用程序的名称空间方案简单快速地理解(只需查看源代码树),并避免像命名空间声明一样的简单错误。

一个好的副作用是每个文件都有自己的私有命名空间,因此在命名时不必担心冲突。

有时名称空间也会变得很烦人,像some.module.far.far.away.TheClass()这样的地方很快就会使你的代码变得很长而且很无聊。在这些情况下,您可以import ... from ...并在当前名称空间中注入另一个名称空间。如果注入导致与您导入的模块发生冲突,则只需重命名导入的内容:from some.other.module import Bar as BarFromOtherModule

Python仍然容易受到循环导入等问题的影响,但它的应用程序设计不仅仅是在这些情况下必须归咎于的语言。

所以python使用了C ++ namespace#include,并在很大程度上扩展了它。另一方面,我没有看到ruby的modulerequire以哪种方式添加任何新内容,并且你有完全相同的可怕问题,如全局命名空间混乱。