- 论坛徽章:
- 0
|
随着系统管理的复杂化和网络运用的扩展,脚本语言在实际编程中的应用越来越广泛。传统观念是:一般的高级语言如C/C++,Java,Delphi 等用来编写主要的功能组件,如java的类和beans,用C/C++写的动态连接库,用VisualBasic写的控件等,而脚本语言如javascript,Vbscript,perl,python,sh 等通常认为脚本语言是介于HTML应用的发展和脚本语言本身的发展,脚本语言的应用早就超出仅仅作为常规编程语言的辅助角色而用来直接开发应用系统,著名的网络流控制系统mrgt就是用perl 开发的。现在的脚本语言,其功能甚至强大到可以和一般高级语言相媲美,而且引入较新的程序机制和技术(如OO和异常处理),加上固有的简单易用,逐渐发展为程序开发的一支主流。脚本语言的基本特征是语法简单,跨平台,类型宽松,解释执行。早期的脚本语言?本文选择现今在自由软件开发里很具代表性和广泛使用的两种脚本语言perl 和python进行比较研究,以期使读者能对这两种脚本语言的基本特点和新发展有一定的了解。
一、 两者初识
Perl(可以看作Practical Extraction And Reporting Language的首字母)语言最早由Larry Wall 开发,原始动机即作为一个文本提取和报告的实用语言,本来是基于UNIX系统,后来发展成能运行于几乎所有的机器体系和操作系统。LarryWall 是坚实的免费软件拥护者,所以perl也成为免费软件之一(但不属GNU),按自由免费软件的一般模式发展和传播(perl中的源代码、标准库文件、可选模块及所有文档都免费提供,并被用户团体所支持)。从1988 年的最初诞生,到现在的perl 6 系列版本,perl能够如此稳健蓬勃的发展是和它自由免费、简单高效(语法上象C 和Unix的sh,解释执行前会简单编译,有点象java)、强可扩展性、数据类型灵活、面向对象以及有强大规范的用户团体交流(CPAN,ComprehensivePerl Archive Network)和幕后支持组织(CPAST, Comprehensive PerlArcana Society Tapestry)分不开的。
Python 最初出现在2000 年前后,名字来源一喜剧团体Monty Python,并无实际代表意义。Python最初由Guido van Rossum 及其合作者设计开发,后来python 开发小组多次重组,最终到DigitalCreations。Python 和perl 一样是在迅速稳定发展,目前的一个著名成功业绩是Digital Creations开发的zope,下一代开放源码应用服务器以及门户工具。从抽象的功能和特点来说,python 是和perl最相像的语言,可能和perl的成功和python 的较晚出现有关。和perl一样,python也是自由免费、简单高效、强可扩展性、数据类型灵活、面向对象的。并且python的功能相对更强大,更易于编写大程序,核心又较小。尽管从抽象的角度,perl和python两者有很大的相似,但作为不同的语言,他们却是又有许多差别,下文从几个主要的方面对两者进行深入的比较研究,尽量能找出它们的异同并对它们一些进行原理和模型层次的探讨。
下面我们先来看一下如何用这两个语言实现最简单的“hello!”程序,先对它们有个大概的印象。在perl 情形,先选择一个你比较喜欢的文本编辑器编写hello.pl 程序如下:
#This is a hello saying demo
print “what is your name?\n”;
$name=;
print “hello $name!”;
在终端或命令行输入perl hello.pl 或直接输入hello.pl 程序将被解释执行,提示输入你的名字,输入名字xiaorong 后程序在屏幕输出“hello xiao rong!”。Python脚本运行有两种方式,一种是交互式的,一种是自动运行脚本。交互式是在命令行输入python,然后进入交互状态(>>>为主提示符,…为从提示符):
ActivePython 2.4.1 Build 245 (ActiveState Corp.) based on
Python 2.4.1 (#65, Mar 30 2005, 09:33:37) [MSC v.1310 32 bit (Intel)] on
win32
Type “help”, “copyright”, “credits” or “license” for more information.
>>> name=raw_input(”what is your name?\n”)
what is your name?
xiao rong
>>> print “hello, “,name,”!”
hello, xiao rong !
>>>
或者编好脚本文件hello.py 如下:
name=raw_input(“what is your name?\n”)
print “hello, “, name, “!”
然后在命令行输入python hello.py 或hello.py 发生的情形和perl 一样。
所以从顶层模型来看,perl 是一个完整的perl脚本输入自动执行。一个perl脚本文件和C/C++源程序类似,由一些语句和语句块组成,语句由“;”分隔,语句块由一对“{}”包括。而python脚本既可自动执行,又能交互式运行。而且python特别的一个地方使它采取缩进来标示分界和层次,不同于一般语言采用”;”和”{}”。这种缩进方式的程序在运行时依赖一个栈来记录逻辑行的INDENT(缩进)和DEDENT(抽出)。在读入脚本文件第一行之前,有个数值0 压入栈底,这个0直到脚本运行完是不会被弹出的。压进栈中的的数值是从底向顶严格增加的。每次读到一行新的逻辑行,先比较它的缩进量和栈顶纪录的缩进量大小,大则表示进入下一层,此时把新的值压入栈并产生一个INDENT记号,小则表示此缩进量和前面某处一样(否则错误,这一点很严格),这时把栈中比它大的量都弹出,产生一个DEDENT记号。如此可以获知每一行所在的层次,并逐行解释执行。
二、 数据类型
脚本语言特点之一就是数据类型灵活,变量无需先声明,类型是靠值来动态确定的。一个变量在一个程序中可以存储不同类型的值。
Perl 支持的内置数据类型有数值(包括整数和浮点)、字符串(包括单个字符)、引用、列表和数组、关联数组(字典),其中前3类属标量,在变量名前面由”$”标示,列表和数组变量名由”@”标示,关联数组名由”%”标示。即变量类型分三类,标量($varname),列表(@varname),关联数组(%varname)。之所以采取这样的标示,很可能是和perl要支持引用相关,因为,要高效支持引用,就得很方便的知道引用的类型。内置数据类型有内置方法支持,如数值的+-*/,字符串的连接、比较和匹配(详见下文的正则表达式讨论),列表的排序和翻转。当然除了内置的类型,还支持新类型,面向对象的自定义对象,在面向对象分析小节使我们详细讨论。Perl的大部分数据对象采取值模型,赋值时复制对象。所以它把引用类型引入(类似C里的指针,但作为引用类型不能进任何算术运算),以方便数据的传递,在子程序小节将讨论引用的应用。引用类型属标量,通过在普通变量前加”\”来获得引用的值,如$rarr=\@array;取值时用类型标示符作用在引用变量前即可,如@$rar 得到数组@array的值,而$$rarr取到@array 第i 元素(数组下表从0 开始)。
Perl 的文件管道句柄用标量来存储,这样很方便对文件和管道的操作。另外,perl 支持别名(alias),即在变量前加“*”,如:
@array=(1,2,3);
*arr=*array;
$arr[0]=2;
print “\@array array\n\@arr arr\n”;
这里有的一个问题是,如果同时有@array,$array,%array 变量则解释器无法区分它们的别名。别名实际是指向符号表项的指针,可以用来方便文件句柄和列表的操作。别名在perl 中又叫类型标识。
Python 支持的内置基本类型有数值、字符串、链表、元组、字典。由于设计时把python定位为完全的面向对象(包括数值类型,直接支持复数类型),对对象操作大多都是靠调用对象的方法来实现。Python的不同类型变量定义无需特殊标示。和perl 不一样,python 大部分对象采用引用模型,但是python不支持引用型变量。象这类采用引用模型的语言,对对象一般分成两类,可以修改(mutable)和不可修改(immutable),不可修改的对象如果发生修改操作报错(但python 返回” NotImplemented ”型值)。
两者的列表数组和链表意义一样,是一个可以同时存多种标量值(整数,字符串等等)的一个线性表。Python的链表允许插入、删除、倒置、排序、切片等操作,长度动态变化;perl的数组只允许在切片和首尾增加删除元素操作。两者的关联数组和字典大致一样,存储的是健值对,允许检索,插入,删除。Python比perl多的元组是不可修改型(不能在其中增加、删除、重新赋值)可以当作列表常量( 在这一点上和perl 的列表更象,它们的输出形式都是“(item1,item2,…,itemn)”,python的链表数组形式为[item1,item2,…,itemn]),而且Python 的元组允许嵌套,相当于广义表(但不允许原地插入删除)。
三、 控制流
perl 支持灵活多样的控制流。有多种条件判断和循环并且支持循环控制和 goto 跳转。首先说一下perl里的条件和条件表达式。同C 语言一样,perl 没有另外的boolean类型,零和空等表示false,其余为ture。条件可能来自比较,函数返回(如读取),状态和环境的判断(如文件是否存在)。特别注意在perl中的有些比较操作,返回3 真值1,0,-1。如数值比较的”<=>”运算符和字符串比较的cmp操作。逻辑运算有与(&&或and 连接)、或(||或or 连接)、非(!或not)、异或(xor)。
Perl 除提供传统的if () { } elsif ( …) { } … else{}条件控制外,还提供了特别的单行条件语句,语法为statement keyword condexpr。其中keyword可为if、unless、while 或until,如: print (”This is zero.\n”) if ($var == 0) 和print (”This is zero.\n”) unless ($var !=0),虽然条件判断写在后面,但却是先执行的,只有满足条件才执行前面的语句。
Perl 支持while 循环 while ( ) { },until 循环 until ( ) { },类C 的for循环,针对列表(数组) 每个元素的循环foreach , 语法为: foreach localvar (listexpr){statement_block; }(此处的循环变量localvar是个局部变量,如果在此之前它已有值,则循环后仍恢复该值。在循环中改变局部变量,相应的数组变量也会改变),do 循环 do {statement_block } while_or_until (condexpr); do 循环至少执行一次循环。
退出循环为last,与C 中的break 作用相同;执行下一个循环为next,与C中的continue 作用相同;Perl特有的一个命令是redo,其含义是重复此次循环,即循环变量不变,回到循环起始点,但要注意,redo 命令在do 循环中不起作用。Goto语句和C 完全一样。
Python 的控制结构相比方式少一些,但能满足通常的需要(因为支持while型的循环)。Python的条件表达式值有Booleans 类型值True 和False 表示,但是Booleans 是作为Integers即整数的子类型,其中Ture 就是1,False就是0。Python的条件值同样由比较,函数,环境和状态产生。有与(and)、或(or)、非(not)运算。Pyhton支持多分支的if…elif…else 条件判断,但书写时必须注意缩排对齐。
Python 支持所谓的对序列中(包括链表和元组)的元素的for 循环“for elements inSequence:”,此时要特别注意在循环过程中不应改变列表形状;python 还支持 while 循环, while expression: block [ else : block ]。Python 循环时除支持类似C中break,continue等跳转控制语句外,还有一种pass 语句,它什么都不做。
四、 解释和环境
很多脚本语言的运行是需要脚本解释器或专门的虚拟机(类似Java的JVM)。而且一般脚本语言不能直接生成可执行文件,所以与环境和操作系统的交互必须通过一些间接的方式,如更多的通过环境变量和特殊变量。这一节我们来看一下perl 和python 的解释执行与环境交互。
Perl 和perl 函数的执行可以被某些环境变量影响,许多环境变量在安装perl时被操作系统或shell自动设置,有些变量可以在脚本运行时被修改和配置。Perl通过内置变量哈希表%ENV提供到当前解释器的环境变量的接口,常用的环境变量包括HOME,PATH,PWD,HOST,USER等,环境变量在不同的操作系统下是有差别的。除了环境变量外,还有一些特殊变量可以用来控制perl 运行或为perl运行提供外部信息,如@ARGV 存储传递给程序的参数,@_存储传递给子程序的参数,$]表保存perl 版本,$@保存最后一次调用eval产生的错误,$? 保存最后一个子进程返回状态,@INC 包含包搜索目录列表,%INC 保存通过do和require得到的程序中每一个文件的列表,%SIG 表示信号句柄(在第十节会详细介绍),STDERR,STDIN,STDOUT分别是标准错误、输入、输出句柄。Perl可以通过system 函数运行程序或外部命令,通过fork 创建子进程。
Perl 在解释执行前实际有一个粗略的编译,解释执行的是操作码(由C 语言编写的一些基本操作指令,类似与Java的虚拟机)。尽管perl 是以脚本语言,可以使用perlcc 命令或其它工具将perl 脚本编译成可执行文件(在windows下.exe文件)。但是,编译成可执行文件不会进一步提高perl代码的执行效率,因为它只是进一步把操作码翻译成机器指令,而不会进一步的优化机器码。
Python 通过sys 和os 两个模块来和系统与环境进行交互,其中sys 模块处理一些与具体系统无关的,而os是针对具体系统的。在sys 模块的argv 数组中可以获得命令行参数,stdin、stdout、stderr分别获得标准输入输出错误的句柄,可以通过sys 里的函数exit 终止脚本的运行。另外sys 模块还提供了一些解释器的信息,如modules是当前被加载的模块,path是个字典,是模块和路径对。os模块的主要功能是确认用户和进程环境,对诸如文件和文件系统这样的外部系统的进行控制和通信。os中的字典environ 提供对当前进程的环境变量的访问,函数system 和exec用于启动其它程序或外部命令。关于进程的控制和通信在第十节并发控制将进一步讨论。
Python 在解释前也是会进行编译的,这一点在使用import语句导入一个的模块时看得更清楚,因为它将源代码”.py”编译成”.pyc”再导入,每次导入都会比较”.py”和”.pyc”文件的时间戳,确定是否需要重新编译。Python 的解释器构架中有一部分就叫做PVM(python 虚拟机);Python脚本解释器核心比较小,很容易用其他高级语言实现,如现在比较流行的Jython,可以看成java 和python 的一个结合。
五、 正则表达式
本来正则表达式一般不在的程序语言设计时考虑。但考虑到脚本语言,尤其是早期的脚本语言如awk,很大程度上是为加强在Unix Shell上的报文处理而设计。从这个程度上来说,perl的内置强大正则表达式处理能力更有传统脚本语言的风格。正则表达式处理一般包括模式串的匹配、提取、替换、分割。而模式串用一个正则表达式来描述。如一般语言里的标示符用[a..z]|[A..Z]( [a..z]|[A..Z]|[0..9])*表示以字母开头任意个字母或数字的字符串。
Perl语言支持对字符串进行模式匹配、替换等丰富的正则表达式相关操作,使用非常方便。和通常郑泽表达式记号差不多:在一对”/”里表示模式串,即/pattern/;”.”代表任一字符,”*”表示前一字符的任意重复,”+”表示前一字符的至少一次重复;[…]中表示字符可选集,[^…]表示禁止字符;特殊字符用”\”转义,”|”表示选择项,{n}指定n次重复等等。“=~”是模式匹配符,表达式$str=~/pat/意思是判断$str里面是否存在”pat”串,返回第一个匹配的串(有的话为非零值即真),可以用它给一个数组赋值,@match=$str=~/pat/g(”g”选项表示匹配所有可能)。下面举一个复杂点例子来说明正则表达式的使用,在一个文本文件里找出所有的emial 地址(xxx@xxx.xxx.xxx)输出:
#prntemail.pl 从email.data 文件中找出所有的email 地址
$file=”email.data”;
if (open($dfile,”$file”)){ #打开文件
@data=<$dfile>; #读数据
foreach $line (@data){
print”@email\n” if(@email=$line=~/[^ \\\.]+\@[^ \\\.\@]+\.[^ \\\.\@]+\.[^ \\\.\@]+/g);
#上一行语句为从每行中找出所有的xxx@xxx.xxx.xxx 形式的串并输出
}
close($file);
}
else{print “error in open file $file\n”}
另一常见用法是用split(/pat/, $str) 将字符串用某模式分成多个单词。此外,=~运算还可以带许多选项来提高使用的灵活性,这里不进一步细说。
Python 语言本身不支持正则表达式操作。进行文本处理时,除内置的字符串基本操作外,可能还需使用string模块。由于字符串对象的方法很丰富,没有正则表达式,也能实现模式的查找、替换,但不如正则表达式方便自然。而要进行正则表达式的操作,则需要re模块的支持。
六、 子程序和作用域
每一个语言都会提供不同层次的抽象机制,常见的有语句块、子程序、自定义数据结构、模块等等。
Perl 提供了子程序、包、模块三种抽象。作用域控制有语句块、子程序、包三种作用域。Perl 里的基本名字空间是包。包和模块详细在下一节再讨论,在这里,我们先说一下子程序和语句块。
Perl子程序涵盖了函数,这两个概念大部分时候不做区分。函数是输入到输出的映射的一段代码,而子程序是完成一项任务的一个子功能的代码,很自然的,所有的子程序可以看成函数。Perl 中没有专门的函数声明,在perl 中用sub[subname] block 来定义子程序。例:
sub hello{
my $name=”xiao rong”; #局部变量$name,$msg 用来传递参数,设定了默认值
my $msg=”hello”;
$name=$_[0]; #待传递的参数自动存储在列表@_中
$msg=$_[1];
print “$msg, $name!”;
}
这定义了一个名字为hello 的子程序。可以通过&hello(name, msg)、hello(name,msg)、do hello(name, msg)或hello name, msg 来调用(如果调用处在定义前,必须用&型)。
默认情况下,所有建立的变量都会确认为在当前包范围内的全局变量。Perl也提供作用域限定符,my 表示局部私有变量,our 表示共享全局变量(即所有包可见),local 建立全局变量副本(对全局变量访问,访问完之后全局变量值复原)。
现在再回过来说一下子程序相关的。可以在任何地方定义子程序,这一点容易从子程序的实际意思理解,这意味着允许子程序任意嵌套。因为函数即是子程序,所以函数调用时参数不必用扩号限定,而可以象在命令行输入一个命令或运行一个程序。
子程序可以被引用,即子程序可以看成一种数据类型,用&标示,可以定义一个引用指向子程序。如上例子程序hello,我们可以如下:
$fun=\&hello;
&$fun(“Mr. Zhang”, “hello”);
定义引用$fun 指向它,并通过解析它调用函数。到此我们可以很好的理解函数的多种调用方式了。
函数参数传递时,所有的参数看成一个列表,都存在系统变量@_里。因为系统会自动把参数变成数组,所以在传递参数是要特别小心。比如2个数组@array1=(1,2),@array2=(3,4),把这两个参数传给一个子程序,实际子程序得到列表(1,2,3,4),或者说在子程序内部可见的是一个列表@_=(1,2,3,4)。传哈希表给子程序,会将哈希表转成键值对顺序列表。如果要传递哈希表或多个数组则可以通过引用来实现:
sub withref{
my ($rarr, $rhash)=@_; #@_中第一个元素是数组引用,第二个为hash 引用
foreach (@$rarr){…}
foreach $key (%$rhash){…}
}
另外一个用来克服传值缺陷的做法是对列表使用别名,类似与C 语言的指针,传的是列表的起始地址,而不是复制整个列表的元素。用“*”运算符来标明,如:
sub alias{
my (*arr)=*_;
foreach (@arr){…}
}
@array=(1,2,4,3);
alias(*array);
因为perl 参数的类型和数目一般不在声明时指定(函数原型的概念是在最近的perl版本中才引入),所以默认子程序是处理不定个数和类型的,当然通过值@_可以得知参数的个数,但一般更常用的是shift 函数。shft函数去列表的第一个值并从中删除,默认对@_操作。
在perl 新近版本可以使用函数原型来限定参数的个数和类型。用相应的类型标示符表示接受的参数的类型,多个参数中间以“;”间隔。另外子程序可以接受一些诸如locked、lvalue、method 等选项。
Perl 预定义了三个子程序,分别在特定的时间执行,它们是:BEGIN 子程序在程序启动时被调用;END子程序在程序结束时被调用;AUTOLOAD子程序在找不到某个子程序时被调用。你可以自己定义它们,以在特定时间执行所需要的动作。若同一个预定义子程序定义了多个,则BEGIN顺序执行,END 逆序执行。
除了子程序外,perl 还支持语句块结构(有时也成为非限定块),可以给语句块命名。块类似与一段循环,不过它值运行一次,可以用last,next,redo 控制。例如下属语句用来模拟C/C++语言中的switch 语句:
SWITCH:{
if($gen=”male”){print “Mr. …”;last SWITCH}
if($gen=”female”){print “Ms. …”;last SWITCH}
}
语句块里同样可以定义变量,前套子程序和语句块。Python提供了函数、模块、和类三个层次的抽象。并且使用名字空间在应用程序中存储对象的信息和数据。关于作用域规则,有所谓的“LGB”规则,即名字空间查找为先局部(local),再全局(global),再内置(built-in);函数和类不会自动继承父作用域。局部是指该抽象层次里,全局指当前python 执行文件,内置是指语言定义,随处可见的一些量和函数。例如:
name=”xiao rong” #global 的name
def hello(): #global 的hello 函数
name=raw_input(“input your name, please:”) #hello 函数内local 的name,built-in的函数raw_input
print “\nhello ”, name, “!”
如果默认的“LGB”规则不能确定名字(出现通常所说的陷阱),则需要特别指定。在下面的子程序说明时将说明这一细节。
在python 里通过def fun(arg1, arg2,…)来定义一个函数。采用命名参数制(相应说法有关键字参数调用),即按名字对应传递;支持默认值;参数也是对象,且采取引用模型。有默认参数值,且有参数缺省时应注意参数排合适的顺序消除歧义。下面定义一个函数,它在一定的字符边框里格式输出问候语:
def greeting(name, greetmsg=”hello”, ch=’*’): #name 为要问候人的姓名
str=”\n”+greetmsg+”, “+name+”!” #greetmsg 为问候语
i=0 #ch为边框字符
while(i print ch,
i=i+1
print str
i=0
while(i print ch,
i=i+1
现在可以通过greeting(“xiao rong”) , greeting(“xiao rong”, “good luck”),greeting(“xiao rong”, “happy birthday”, “#”)和greeting(“xiao rong”,ch=“#”)等多种方式来调用函数greeting。
Python 可以在任何地方定义函数,下面举一例子说明,并说明上面说的陷阱:
def greet(name, msg):
print makemsg(msg)
# def makemsg(m):
# str=m+”, ”+name+”!” #根据LGB 规则,name 名字无法确定,故这种写法
# return str #出现陷阱,不正确
这种情况,一般可以通过参数传给子程序使之能访问到。Python 的函数调用必须用带扩号,否则输出的是函数在系统中的信息。在子程序里可以利用global 关键字声明变量在全局定义。转载自:perl小站 |
|