本节书摘来自异步社区《Ruby程序员修炼之道》一书中的第1章,第1.3节Ruby扩展和编程库,作者【美】David A. Black(戴维 A. 布莱克),更多章节内容可以访问云栖社区“异步社区”公众号查看。
1.3 Ruby扩展和编程库
本节的要点并不是关于Ruby标准库的参考。曾在引言中解释过,本书的目标不是编写一本Ruby语言的参考文档,而是教会读者使用Ruby语言并掌握它,并最终拓宽视野。
相应地,本节的目标是讲述扩展的工作方式,即如何使用Ruby运行这些扩展、它们之间技术实现的不同,并最终能让用户自己编写扩展和库文件的扩展架构。
随Ruby发布的扩展通常全部作为标准库来引用。标准库包括为不同项目和任务所提供的扩展,如数据库管理、网络、数学领域、XML处理等。标准库精密的结构每次改变,哪怕只有一点,也都要随着Ruby新版本而发布。使用最多、最广泛的库,已经证明了其存在的价值,所以通常都趋于稳定。
使用扩展和库的关键是require方法,与之密切相关的是load方法。这些方法让读者可以在运行时加载扩展,包括自己编写的扩展。我们将通过加载内置的扩展来学习它们并拓展我们的视野。
1.3.1 加载外部文件和扩展
可以手动把程序存储在单一文件中,但如果有成百上千行或者成千上万行的代码,这将是一种负担而不是优势。尽管如此,可以将程序分解为能运行的不同文件。在Ruby中使用require和load方法将会使得这一过程变得容易。这里先阐述load的使用,它是两个方法中设计得最简单的一个。
功能、扩展,还是库?
用户在运行时加载到程序中的程序代码有许多不同的名称。功能(feature)是最为抽象的,很少听到过有人说“请求一个功能”(requiring a feature,这里使用require)这样特殊的使用方式。库(library)是更为具体和通用的。它意味着存在一组真实的易于编程的代码,并能够被加载调用。扩展(extension)可以被任意可加载的附加库所引用,但是在Ruby中这通常意味着它们不是用Ruby而是用C语言编写而成的。如果要说正在编写的是Ruby扩展,就意味着已经假定那是用C语言编写的。
要试一下这个例子,需要将程序分写在两个文件中。第一个文件,名为loaddemo.rb,应该包含如下代码:
puts "This is the first (master) program file."
load "loadee.rb"
puts "And back again to the first file."
当它遇到load方法的调用时,Ruby就会加载第二个文件,这个文件名为loadee.rb,包含如下代码:
puts "> This is the second file."
这两个文件应该放在同一个目录下(假定存在示例代码目录)。在命令行中运行loaddemo.rb,可以看到如下输出结果:
This is the first (master) program file.
> This is the second file.
And back again to the first file.
这个结果可以追溯到执行文件的某一行,以及执行的顺序。
为了在loaddemo.rb中加载名为loadee.rb的文件,将它作为参数:
load "loadee.rb"
如果将要加载的文件就在工作目录中,Ruby将能够根据名称找到它。如果不是,Ruby将在加载路径(load path)中查找它们。
1.3.2 加载位于默认加载路径中的文件
Ruby解释器的加载路径是一系列目录,请求加载时,Ruby会在这些目录中搜索文件。可以使用$:(美元符号及冒号)检查加载目录下的这些子目录的名称,看到的结果取决于用户所使用的平台。在Mac OS X上,一个典型的加载目录如下(这个示例包含了.rvm的路径,它是Ruby版本管理器所决定的Ruby版本的路径):
在你的电脑中,“ruby-2.1.0”左边的部分可能会有所不同,如“/usr/local/lib/”,但是子目录的基本模式都是一样的。当加载一个文件时,Ruby将会自上而下地在每一个子目录中搜索。
**注意
当前的工作目录,通常使用一个点(.)表示,这不会包含在加载目录中。加载命令的作用如前面所介绍,这只是一个特例。**
可以在load命令中使用代表上级目录的双点(..)符号导航到相对目录:
load "../extras.rb"
注意,如果在程序运行中改变当前的目录,相对目录的引用也将会改变。
**注意:
记住load是一个方法,在程序文件中,只有Ruby遇到它的时候才会执行。Ruby不会搜索整个文件去执行load命令。也就是说,当Ruby解释器遇到它的时候,它才会去寻找它要加载的文件。这意味着需要加载的文件名可以在运行时动态地决定。甚至可以在条件语句中包含一个load指令的调用,让它只有在条件为true的时候才会被执行。**
同时也可以强制指定load搜索一个完全限定的文件路径,而不管加载路径的内容。
load "/home/users/dblack/book/code/loadee.rb"
当然,这比起使用加载目录或者相对路径来说兼容性会差一些,但是这可能会很有用处,尤其是如果拥有一个字符串变量,其中包含一段绝对路径并想要加载它的时候。
load命令总是会加载所请求的文件,不论这个文件是否已经加载过。假如一个文件在几次加载过程中发生改变,那么最新版本的文件将优先使用并覆盖之前加载的版本。尤其是在irb会话中,当在编辑器中修改一个文件时,想要立刻测试修改的效果,这将非常有用。
另一个加载文件的方法是require,它同样也搜索默认的加载路径中的目录。但是require有一些load不具有的特点。
1.3.3 请求功能
load和require最大的不同在于,require就算调用多次也不会重新加载已经加载过的文件。Ruby会持续追踪已经被请求的那些文件而不会重复加载它们。
require比起load来说更为抽象。严格来说是不能请求一个文件的,而只能请求一个功能。一般来说,做到这一点甚至不用指定文件的扩展名。为了验证之前所述,将loaddemo.rb中的这一行进行修改,从
load "loadee.rb"
修改为
require "./loadee.rb"
当运行loaddemo.rb时,即使提供了需要加载的文件的完整文件名,得到的结果也可能与之前相同。
通过把loadee看作一个“功能”而不是一个文件,require对用Ruby编写的扩展和使用C语言写成的扩展都用一样的方式。另外,.rb扩展名的文件与其他扩展名为.so、.dll或者.bundle的文件使用方式也是一样的。
指定工作目录
require不能辨识出当前的工作目录(.)。用户可以显式地指定它,例如:
require "./loadee.rb"
或者可以使用数组添加运算符(<<),把当前目录添加到加载路径当中。
$: << "."
这样,就不必在调用require的时候显式地指定工作目录了。
require "loadee.rb"
也可以给require指定完全限定的路径,和使用load一样,把文件或者功能加载进来。读者也可以混合使用该规则,例如,即使是将静态路径与路径末端更为抽象的语法功能混合使用,以下语法也是可以正常运行的,例如:
require "/home/users/dblack/book/code/loadee.rb"
尽管load很有用,尤其是当加载多于一个文件的时候,但是require是一个日常使用的技术,用于请求Ruby扩展和库,不论是标准库还是第三方库。与加载loadee文件相比,请求标准库的功能要简单得多,只使用require就可以请求想要的任何库文件。之后,扩展中新的类和方法都可以使用了。下面的例子中,给出了请求前后在irb会话中的不同。
>> "David Black".scanf("%s%s")
NoMethodError: undefined method `scanf' for "David Black":String
>> require "scanf"
=> true
>> "David Black".scanf("%s%s")
=> ["David", "Black"]
第一次调用scanf的时候失败并报错。但是在调用了require之后,没有编程人员的介入,"David Black"这个字符串对象也响应了scanf消息。(在这个例子中,使用空格为隐式分隔符,用于把原字符串抽取为两个连续的字符串)
1.3.4 require_relative指令
require_relative是第三种加载文件的方式。这个指令会搜索相对于所在文件的目录来加载功能。因此在前一个例子中,可以这样:
require_relative "loadee"
这样就可以不用把当前目录加入加载路径中。当需要导航到某个本地目录结构中的时候,require_relative是一个便捷的方式,例如:
require_relative "lib/music/sonata"
接下来将对随Ruby发布的命令行工具进行一次测验,以此总结这一章所学到的知识。