require与import加载代码的优缺点是什么?
Ruby语言使用的是require
,而Python语言使用的是import
。这两者在工作原理上有很大的不同。虽然我更习惯使用require
,但我发现有些地方我更喜欢import
的方式。我很好奇大家觉得在这两种方式中,哪些地方特别简单,或者更有趣的是,哪些地方其实比想象中要难。
特别是,如果你要设计一种新的编程语言,你会怎么设计代码加载的机制呢?在你的设计选择中,哪些优点和缺点会对你影响最大呢?
4 个回答
声明一下,我并不是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
这样的代码加载机制,但语言本身没有全局命名空间会怎么样?也就是说,所有东西都必须有命名空间,但开发者可以完全控制类定义在哪个命名空间中,而这种命名空间的声明是在代码中明确写出的,而不是通过文件名来决定。或者,定义在全局命名空间中的东西会产生警告。这是一种两全其美的方法,还是说我遗漏了什么明显的缺点?
一个很不错的特点是,require
其实是定义在Kernel
里的一个方法。所以你可以重写它,自己实现一个Ruby的包管理系统,这就是Rubygems所做的事情!
PS:我并不是在推销“猴子补丁”,而是想说Ruby的包管理系统是可以被用户重写的(甚至可以做到像Python的系统那样)。当你在写一种新的编程语言时,不可能所有的地方都做到完美。因此,如果你的导入机制可以在语言内部完全扩展(向各个方向扩展),那对未来的用户来说是最好的服务。如果一种语言不能在内部完全扩展,那就是一种进化的死胡同。我觉得这是Matz在Ruby上做对的事情之一。
在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”,里面有子模块machine
和interface
,我们希望把它们作为一个方便的命名空间直接放在包名下,这就是我们可以在“包定义”文件process/__init__.py
中写的内容:
from process.interface import *
from process.machine import Machine, HelperMachine
这样我们就把通常可以通过process.machine.Machine
访问的内容提升到了process.Machine
。同时,我们也以非常明确的方式把process.interface
中的所有名字添加到process
的命名空间中。
我提到的Python的import的两个优点就是:
- 清晰:使用
import
时你包含了什么 - 明确:你是如何修改自己模块的命名空间的(无论是为了程序本身还是为了其他人导入)