七、程序包
七、程序包 返回目录页
1、运行目录与规则库目录
2、目录和符号
3、搜索顺序
4、载入程序包
5、自制程序包
6、标准程序包
表达式的计算过程,是个递归的过程:
一边在规则库中搜索规则(rule),一边进行变换(transformation),直到无规则可用为止。
-------------------------------------------------------------------------
1、运行目录与规则库目录
当我们打开或新建一个笔记本文件,输入代码,然后运行。表面上看起来很简单。
但是呢,背后隐藏着太多的东西。
前面第四章,我们讲了表达式的运算过程,然后总结出,MMA的核心编程思想是:规则编程。
规则编程这套系统具体是怎么构建的呢?规则库在什么地方呢?
如果先浏览一下这一章,再去看第四章,可能更容易明白。
----------------------------------------
运行目录。
在同一个笔记本文件中,我们可以在不同的运行目录下编程。
Begin["A`"]
$Context
End[]
$Context
得:Global`
Begin["A`"] 新建目录A,并进入了目录A
$Context 是个全局变量,返回MMA进程中的当前运行目录名
End[] 退出目录A,返回进入目录A之前那个目录
` 是倒引号或重音符字符(ASCII 码是 96),在这里是运行目录标志。
Global` 是全局运行目录,一开始打开或新建笔记本文件时,这个运行目录就自动打开。
A`x="textA"
B`x="textB"
Global`x="textGlobal"
x
三个变量x,分别在不同目录下,它们的值都不同。
如果用全名,可以像用一般其它符号一样使用它们。
如果变量多时,给变量分类很方便,分别放在A、B...目录。当变量名相同时,也不会冲突。
如果每次用全名,比较麻烦,所以我们经常不用全名,而直接写变量:Global`x与x,返回同一值。
----------------------------------------
规则库目录
当我们输入:Global`x="textGlobal" 时,定义了一个规则,在运行目录下,建立了一个变量x,值是"textGlobal"。
那这个规则,保存在什么地方呢?即规则库在什么地方呢?
答案是:也是保存在Global`目录中。
所以对于Global`目录来说,既是运行目录,又是变换规则库目录。
在帮助文档中,翻译成了上下文(context),看起来非常别扭。我们在这里,称之为运行目录,或者规则库目录,有时简称为目录,比较好理解。
-------------------------------------------------------------------------
2、目录和符号
运行目录与操作系统中的文件目录,在结构上是很像的,都是树形结构。
文件目录下,可以有文件目录与文件。
一样地,运行目录下,可以有运行目录与符号(定义、变量、规则,名称很多,指的是同一东西)。
一些文件操作的概念也很相似,无非在MMA中,以函数的形式来操作。
$Context
Context[]
都是返回当前目录名。
Context[x]
返回一个符号的目录。
Clear[x]
x
清除一个符号的值
Remove[x]
?x
从系统中完全删除一个符号
++++++++++++++++++++++++++++++++++++++++++++++++++++++
Clear["form"] 删除名与 form 匹配的所有符号的值
Clear["context`*"] 删除在指定目录中所有符号的值
Remove["form"] 完全删除名称与 form 匹配的符号
Remove["context`*"] 在指定的目录中删除所有符号
如:
xx = 3;
xx
Clear["x*"];
xx
xx = 3;
x = 2;
Clear["Global`x*"];
xx
x
xx = 3;
xx
Remove["x*"];
?xx
xx = 3;
x = 2;
Remove["Global`x*"];
?xx
?x
++++++++++++++++++++++++++++++++++++++++++++++++++++++
Names["form"] 给出与 form 匹配的符号列表
如:
xx = 3;
x = 2;
Names["x*"]
Names["System`Ab*"]
NameQ["form"] 测试是否有与 form 匹配的已存在符号
如:
NameQ["System`Ab*"]
NameQ["System`AB*"]
(* 前两句很好用,可以检查符号有木有用过 *)
Contexts[] 所有目录组成的集合
Contexts[]
Contexts["string"] 给出匹配字符串的目录列表
Contexts["A*"]
-------------------------------------------------------------------------
3、搜索顺序
运行目录与规则目录,都是按树形结构安排的。这点已经清楚了。
那么当程序运行时,要找规则来匹配。规则目录有很多,按什么顺序来搜索规则呢?
有一个全局变量:
$ContextPath
用来决定目录的搜索顺序。
返回可搜目录的列表。这个列表中,目录是按一定顺序排列的。
那什么叫“一定顺序”?
当文件刚建立时,有个默认的顺序:
$ContextPath
得:{..., "System`", "Global`"}
目录"System`" 在目录 "Global`" 之前,意味着先搜索"System`"目录,再搜索"Global`"目录。
("System`"目录中,存放着所有内置函数。)
那在$ContextPath中的各个目录中,还是搜索不到,咋办?
那就搜索当前的工作目录($Context)。当前的工作目录,就是当前的规则库目录。
那还是木有,咋办?
那就好办了,工作完毕,表达式计算完毕,原样输出。
规则总的搜索顺序就是:MMA总是先搜索$ContextPath列表中的规则库目录,再搜索当前的工作目录。
----------------------------------------
关于搜索顺序的讨论。
以上总的搜索顺序是说明文档中说的。而在一本非常好的入门书的两个版本中,说法却均与之相反。
(
《数学软件Mathematica入门》第一版的中文版。
An Introduction To Programming With Mathematica 3Ed
)
到底谁对谁错?
----------------------------------------
最好的办法是实验验证。在验证之前,要说个事。
$ContextPath作为全局变量,是可以任意修改的。这意味着,MMA完全允许用户自定义搜索顺序。
(****************
$ContextPath = {}
1+2
*****************)
这是个破坏性实验,所有规则库报废,1+2是算不出来了。
而且无法恢复,因为这个笔记本文件已经丧失一切计算功能(无任何规则可以匹配)。
那再新建一个笔记本文件好了?还是不行。
只能重启MMA了。
(严重警告,在做这个实验前,把前面工作保存好!)
还好,重启MMA正常了。下面开始做正常试验:
A`x = "textA";
B`x = "textB";
Global`x = "textGlobal";
$ContextPath = {"B`", "A`", "System`", "Global`"};
x
Begin["A`"];
x
End[];
$ContextPath = {"A`", "B`", "System`", "Global`"};
x
Begin["A`"];
$ContextPath = {"Global`", "A`", "B`", "System`"};
x
$ContextPath = {"Global`", "System`"};
y = "textAy";
y
End[];
y
Context[y]
观察结果,得出结论:
“MMA总是先搜索$ContextPath列表中的规则库目录,再搜索当前的工作目录。”
这句话是完全正确的,但不完整。完整的表述是:
MMA总是先搜索$ContextPath列表中的规则库目录,再搜索当前的工作目录——如果还没找到,则搜索剩下的没有搜索过的工作目录。
根据以上的搜索过程,得出计算过程:...如果找到,则马上进行替换,如果找不到,则原样输出。
这只是完全匹配的情况。如果考虑到模式匹配,要在所有可能的匹配中,找到最匹配的。。
MMA是个复杂的系统。
-------------------------------------------------------------------------
4、载入程序包
在使用MMA编程中,很多时候,内置函数(共有3000个左右)已经够用。
但在更多时候,当我们有更多的需求的时候,希望有更多的函数可用。
每个MMA标准版,都包含了标准程序包。
程序包(package)是函数的集合,即把函数们打包了。
要使用程序包,必须要先载入。载入之后,在程序包中函数的使用上,与内置函数已经没有区别。
如果标准程序包还不能满足用户的需求,用户可以自定义程序包。
自定义程序包在使用时也一样要先载入。然后在使用上与内置函数也没有啥区别。
所有程序包以外部文件的形式在操作系统的文件管理系统中存放,一般文件名为这种形式:package.m
----------------------------------------
$Packages
给出当前MMA进程中所加载的所有程序包对应的规则库列表。(包含Global`目录)
一般来说,程序包对应的外部文件的文件名,与程序包内定义的规则库目录名称,保持统一。
$ContextPath
返回可搜规则目录的列表。前面已经使用过。
$ContextPath可用户自定义,按需要改变内容。但$Packages
是受到保护的,不可以改变。
一般程序包加载成功,系统不会有任何提示。但加载不成功,则会有错误提示。
加载成功意味着:
A、程序包对应规则目录,已经添加到$Packages列表最前面。
B、程序包对应规则目录,已经添加到$ContextPath列表最前面。
Needs["ComputerArithmetic`"]
$Packages
$ContextPath
ComputerArithmetic`是标准程序包之一,外部文件(ComputerArithmetic.m)存放目录:
...Mathematica9\AddOns\Packages\ComputerArithmetic\
加载成功后,可以用Names函数,来查看加载的程序包中有哪些函数可用:
Names["ComputerArithmetic`*"]
然后可以查询函数的功能。
?Arithmetic
然后,还可以打开帮助文档。(点击右下角的 >> )
----------------------------------------
加载程序包
Needs["context`"] 调用 Get["context`"]。
所以我们只要学习Get函数。
<< 是Get函数的语法糖。
$Path
给出在试图找到一个外部文件时搜索的缺省文件目录列表。
运行一下,看看输出。
$Path表是可以修改的。但要注意,如果给它改成了空表,则再也无法载入外部文件了。
Get函数,可以加载很多类型的外部文件。无一例外地,总是搜索每一个$Path表中的每个元素。
我们在这里,只关心Get["context`"]这种类型的情况,即加载程序包文件context.m,得到context`规则库目录。
具体说,Get["context`"]分两种情况:
A、加载文件
(潜规则是,想加载程序包con.m文件、得到con`规则库目录,把两者取同名:这里名字都是con。)
Get["con`"]
运行时,在所有$Path表中的每个元素(即文件路径)下,寻找con.m文件。一旦找到,就停止寻找,然后载入con.m文件。
有时候,文件都不在这些搜索路径之下,可以这样写:
Get["dir`con`"]
那就是在搜索路径之下,寻找dir文件夹下的con.m文件。找到就停止寻找,然后载入。
还有一种写法,与$Path表中路径无关,直接指定若干寻找目录,格式是:
Get[name,Path->{dir1,dir2,...}]
比如写成这样:
Get["SayHi`", Path -> "X:/aaa"]
即SayHi.m在X:/aaa/目录下,可以这样直接找到并载入。
要注意的一个事是,目录作为一个字符串的形式表达
"X:/aaa/"
"X:\\aaa\\"
这两种形式都可以,但不能写成:
"X:\aaa\"
因为类似于C语言中的字符串中,\已经用于转义,请观察:
"X:\naa\"
得:
"X:
aa"
\n作为一个整体,转换成了换行符。
B、加载目录
在$Path各路径下,搜索文件与搜索目录是同时进行的。
当文件在$Path各路径下找不到时,找context目录。
(这里又有个潜规则,想加载目录con,得到con`规则库目录,则把两者取同名:con)
加载目录的过程是,当找到con目录时,在con目录下寻找Kernel目录中的init.m文件。然后运行init.m文件中的语句。(加载目录只是用于表达一种加载方法。目录无法加载,真实加载的还是文件,通过init.m中的语句。)
哪怕init.m中一句语句也没有,MMA也会认为工作完毕,而停止寻找。
如果找不到init.文件,则在con目录下找匹配的程序包文件来试图载入。
标准程序包,都是以这种“加载目录”的形式载入的,都通过init.m文件。
所以标准程序包对应的外部文件,都这样组织:
con目录下,有若干程序包文件,然后还有一个文件夹Kernel,里面有个init.m文件。
可以用FindFile函数,找到init.m文件的具体位置:
Needs["ComputerArithmetic`"]
FindFile["ComputerArithmetic`"]
----------------------------------------
DeclarePackage
DeclarePackage["context`",{"name1","name2", ...}]
声明如果任何具有指定名称的符号被使用的话,则 Needs["context`"] 应该自动执行
$ContextPath
DeclarePackage["StatisticalPlots`", "ParetoPlot"]
ParetoPlot[{a, b, c, d, d, d, e, d, e, e, f, a, b, c}]
$ContextPath
这也是载入程序包的一种方法。
----------------------------------------
自动加载程序包
假如一个SayHi.m,存放在X:\aaa\目录中时(操作系统为Win系列),想要MMA每次启动时,都自动加载,咋办?
$BaseDirectory
$UserBaseDirectory
这两个文件夹中(以下操作,在这两个文件夹中的任一个,都可以),都有autoload文件夹与Kernel文件夹。
Kernel文件夹中的init.m文件能够在mma启动的时候自动加载。
用记事本打开init.m文件,如果没有,则创建一个init.m文件。
添加下面内容:
Get["SayHi`", Path -> "X:/aaa"]
启动MMA:
$Packages
$ContextPath
可以看到,SayHi`规则目录都在里面了。
还有一种方法,是在autoload文件夹中做文章。
在autoload文件夹下,建立一个子文件夹:SayHi
然后再在SayHi文件夹下,建立一个子文件夹:Kernel。
然后再在Kernel文件夹下,建立一个init.m文件。(只要先用记事本建立一个txt文件,然后改名就行)
init.m文件中,只有一句话:
Get["SayHi`SayHi`"]
最后,把SayHi.m文件,从X:/aaa文件夹,copy到SayHi文件夹下。
完工。
启动MMA:
$Packages
$ContextPath
可以看到,SayHi`规则目录都在里面了。
?SayHi`*
可以看到里面的函数了。
-------------------------------------------------------------------------
5、自制程序包
程序包可以自制。
一方面,当程序比较大型的时候,自制程序包可以使代码容易维护,也更容易编写。
另一方面,通过学习程序包的自制过程,可以更容易理解标准程序包。
----------------------------------------
下面我们来制作自定义程序包:Say.m
选择菜单:文件/新建/程序包。
然后在弹出窗口中开始编辑文本。
输入以下内容:
$ContextPath
BeginPackage["Say`"]
$ContextPath
Begin["`Private`"]
$ContextPath
End[ ]
$ContextPath
EndPackage[ ]
$ContextPath
然后保存程序包,假设保存为:X:/aaa/Say.m
然后点击右上角的“运行程序包”,看下面的输出结果。
这个程序,主要是为了观察$ContextPath的内容变化。
BeginPackage["Say`"] (* {"Say`", "System`"} *)
EndPackage[] (* 恢复到之前内容 *)
这两句,改变了$ContextPath的内容。
而Begin["`Private`"]不会改变$ContextPath的内容。
结合第3节内容,我们马上了解了程序包的工作方式。
----------------------------------------
再把内容修改为:
(* ^ *)
BeginPackage["Say`"]
Say::usage = "这是Say程序包。"
Begin["`Private`"]
Say := Print["SayOut"]
SayA:= Print["SayAOut"]
End[ ]
SayB:=Print["SayBOut"]
EndPackage[ ]
点击保存。然后再点击“运行程序包”
注意三个地方的颜色。Say/SayA/SayB
注意把下图所指的地方,设置成鲜艳的颜色。
----------------------------------------
在笔记本文件中,输入:
Get["Say`", Path -> "X:/aaa"]
$Packages
$ContextPath
可以看到,Say程序包载入进来了。
?Say`*
可以看到,Say::usage部分的提示信息出来了。
Names["Say`*"]
得:{"Say", "SayB"}
Say
SayA
SayB
可以看到,仅SayA没有被替换。
因为SayA的定义,躲在
Begin["`Private`"]
SayA:= Print["SayAOut"]
End[ ]
之中,是外部不可见的。
所以一些临时变量啊、局部变量啊,可以躲到这里面去。
没有赋值的全局符号,可以用特殊颜色显示,这个非常好用,如图:
----------------------------------------
前面不断用到的SayHi.m,就更简单了:
X:/aaa/SayHi.m
(* 22:35 2016-11-2 *)
BeginPackage["SayHi`"]
SayHi::usage = "这可是我的第一个程序包啊!"
SayHi := Print["一定载入成功!"]
EndPackage[ ]
笔记本中:
Get["SayHi`", Path -> "X:/aaa"]
$Packages
$ContextPath
?SayHi`*
Names["SayHi`*"]
SayHi
一边打开程序包文件,进行改写。
一边在笔记本文件中进行载入输出等调试。
这是可以的,不用不断重启MMA。
只是要注意多按“保存”、“更新”。
最后强调一句:多观察字符的颜色很重要。
-------------------------------------------------------------------------
6、标准程序包
帮助文档中有标准程序包的详细说明:
guide/StandardExtraPackages
Mathematica 9.0 标准程序包
只是没有翻译成中文。
每个程序包,都可以去找到源代码,在以下目录中:
...Mathematica9\AddOns\Packages
内容非常丰富。
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MMA的基本入门内容介绍完毕,接下来做什么呢?
要么去看帮助文档:函数浏览器。MMA的内置函数,有三千个左右,我们这里只介绍了一小部分。去看按功能分类的函数浏览器,可以扩大我们的函数量(如同学英语时扩大词汇量)。
要么去看目录页中介绍的类似的书,去看MMA的应用代码。在应用中扩大函数量(用F1很方便啊)。
+++++++++++++++++++++++++++++++
扩展阅读:木有。有时间的话,去浏览一下标准程序包的代码
对了,MMA老板,一个给整个世界建模的奇人,是如何介绍MMA的呢?
看个视频吧。
T o p