免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 6339 | 回复: 8
打印 上一主题 下一主题

Perl 和Python 的语法比较研究 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-08-21 04:56 |只看该作者 |倒序浏览
  随着系统管理的复杂化和网络运用的扩展,脚本语言在实际编程中的应用越来越广泛。传统观念是:一般的高级语言如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 “\@arrayarray\n\@arrarr\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小站

论坛徽章:
0
2 [报告]
发表于 2009-08-21 04:56 |只看该作者
  七、 包和模块

  任何一种程序语言要支持大的程序项目开发,必须有一套组织方法,把一个大的系统分解为一些小的单元,现在比较流行的一种做法是分模块和包。但是模块和包这两个概念在不同的语言里面差别很大。Perl 的包主要用途是来确定一个名字空间,每个包关联一个和包同名符号表(为哈希表结构%packagename)。符号表包括包的一些局部符号、类型索引符(即静态变量,相当于通常的常量)和全局可见特殊变量,这样使不同包里的符号不和其它地方冲突,这样的符号即为私有数据。相当于C++里的 namespase,包和包之间可以相互引用,但必须指明引用的名字所属包,用”::”运算符,和C++也很像,默认包是main 包,用package 定义或切换包,例如:

$mainvar=10; #main 包里的变量$mainvar
our $var=14; #共享全局变量,随处可见
package mypack; #定义新包mypack 并切换进去
$myvar=main:mainvar+10; #定义mypack 里的变量并引用main 包里的$mainvar 给他赋值
package main; #切换到main 包里
$mainvar=mypack:myvar+$var; #引用mypack 里的$myva 和共享变量$var 给$mainvar赋值
package mypack; #切换到mypack
$myvar=main:mainvar+10;
print $myvar, main:mainvar;

  可以随时在任何地方切换包。
  Perl 模块是一个定义了大量的函数或变量的有返回值1 的perl 资源文件,以后缀名.pm 标识,其中定义的函数或变量可以被其它perl 文件导入使用。一个典型的perl 模块mymod 是这样的:

package mymod;
require Exporter; #使用Exporter 模块来定义本模块的导出符号
@ISA=qw/Exporter/; #将模块加入到@ISA 数组供系统搜索
@EXPORT=qw/fun1 fun2/; #把本模块导出的所有方法添加到@EXPORT 记录
@EXPORT_OK=qw/$var @var/; #把本模块导出的所有变量添加到@EXPORT_OK 记录
our ($var,@var);
sub fun1{
…}
sub fun2{
…}


1; #返回值1 必须,否则导入本模块将失败

  假设此文件存为path/mymod.pm , 则可以用use path::mymod 或require path::mymod 导入使用。两者的不同是,use 将所有mymod 中导出的符号导入到当前包,而require 导入的是从他们的定义包,如要使用mymod 的fun1,前者只需简单调用fun1(…),而后者则需指定模块名mymod::fun1(…)。两种导入的另外一种区别是,use 语句在解释和编译时执行,而require 语句在解释执行时才执行,所以require 语句可以用于动态导入模块(require “$modname”,$modname是一个变量)。

  Python 把模块和包两个概念统一了,即模块就是包,有时也称为模块包。在python 中创建新的模块和创建一个普通脚本文件无二,即创建的任何python 交脚本都可以当模块来使用。所需要做的,只是将文件存到系统可以搜寻到的位置。到入模块使用import 语句,类似java,但不支持*匹配。例如定义一个含两个方法的模块mymod 存储为path/mymod.py:

def funprnt():
print “this is printed by funprnt() in modular mymod”
def fun2(…):
#code goes here

  这样可以在其他文件或命令行交互时导入该模块import xxx.xxx..mymod 或from xxx.xxx..mymod import funprnt(导入特定实体),然后引用里面的方法和变量也类似java 采用全名,如调用funprnt 前一种导入方式为xxx.xxx..mymod.funprnt()后一直接用funprnt()即可。每个python 模块都有标识变量__name__可以被通常代码访问,有初始代码段__init__.py 每次导入将自动执行,还有其它变量如__all__等可以用来控制模块行为。

  八、 面向对象

  面向对象能提供更好的安全性和代码可重用型,一般具有数据封装性、继承、方法动态绑定的特点。在具体的语言,面向对象的模型和实现大不一样。下面我们来简单看一下perl 和python 里面的面向对象机制。

  有了包和引用的概念后,我们就很容易来理解这perl 的面向对象了。Perl的类就是一个包,对象就是和类绑定的数据引用。对象和普通的引用的差别是,它是通过在创建时bless 绑定。我们来看一个简单例子,学生信息类STU,保存有学生的姓名,年龄,并能调用方法prntinfo 打印出这些信息:

package STU;
require Exporter; #使用(继承,通过@ISA 数组)Exporter,才能导出
@ISA=qw(Exporter);
@EXPORT=qw(prntinfo); #只有在EXPORT 数组中的方法在别处可见
sub new{ #构造函数
@info=($_[1],$_[2]);
$this=\@info;
return bless $this; #将数据与类关联
}
sub prntinfo{ #定义方法prntinfo
$self=shift;
print “name: $$self[0]\t age: $self[1]\n”;
}
1;

  存储为STU.pm,下面的程序test.pl 使用这个类:

use STU;
$st=new STU(“xiao rong”,22);
$st->prntinfo();

  输出的结果为:name: xiao rong age: 22。

  所以,perl 的类是包,带有构造函数new。对象只不过是同该包绑定的数据引用(我们可以查看$st 的值即print $st,输出结果为STU=ARRAY(0×200f99c),与普通引用的值不大一样),在上例中数据引用是一个列表,一般对象数据为哈希数组。

  Perl 类的方法只不过是一个perl 子程序而已,也即通常所说的成员函数。Perl的方法定义不提供任何特殊语法,但规定方法的第一个参数为对象或其被引用的包。Perl 有两种方法:静态方法和虚方法(静态方法类似与C++的,但虚方法不同)。静态方法第一个参数为类名,虚方法第一个参数为对象的引用。方法处理第一个参数的方式决定了它是静态的还是虚的。静态方法一般忽略掉第一个参数,因为它们已经知道自己在哪个类了,构造函数即静态方法。虚方法通常首先把第一个参数 shift 到变量self 或this 中,然后将该值作普通的引用使用。Perl的对象方法调用模型是生成一个方法对象,即从对象和它调用的子程序生成一个新的“方法”,然后按普通的子程序执行。

  Perl 的类封装通过包的作用域限制和子程序的作用域限制以及在@EXPORT数组来控制,继承主要是通过@ISA 数组来实现,在@ISA 数组中的所有类都是基类,但基类数据不会自动继承,需要程序员写相应代码。我们用一个例子GSTU继承前面的STU 类来说明:

package GSTU;
require Exporter;
require STU;
@ISA=qw(Exporter,STU);
@EXPORT=qw(prntinfo);
sub new{
$this=new STU($_[1],$_[2]); #调用基类STU 的构造函数来继承基类的数据
$$this[2]=$_[3]; #列表增加一项研究方向
bless $this;
}
sub prntinfo{ #重载方法prntinfo
$self=shift;
$self->STU::prntinfo();
prnit “researching area$self[2]\n”
}
1;

  上面的代码还涉及到了方法的重载(它们属于不同的名字空间)。继承实际是提供了一个方法的包搜索空间,如果在本类和基类中找不到,则调AUTOLOAD 子程序,再不就看UNIVERSAL 子程序是否存在并调用或报错。

  Perl 跟踪对象的链接数目,当某对象的最后一个应用释放到内存池时,该对象就自动销毁。对象的析构发生在代码停止后,脚本将要结束时。对于全局变量而言,析构发生在最后一行代码运行之后。如果你想在对象被释放之前获取控制权,可以定义DESTROY()方法。DESTROY()在对象将释放前被调用,使你可以做一些清理工作。DESTROY()函数不自动调用其它DESTROY()函数,Perl 不做内置的析构工作。如果构造函数从基类多次bless,DESTROY()可能需要调用其它类的DESTROY()函数。当一个对象被释放时,其内含的所有对象引用自动释放、销毁。 一般来说,不需要定义DESTROY()函数,如果需要,其形式如下:

sub DESTROY {
#
# Add code here.
#}

  因为多种目的,Perl 使用了简单的、基于引用的垃圾回收系统。任何对象的引用数目必须大于零,否则该对象的内存就被释放。当程序退出时,Perl 的一个彻底的查找并销毁函数进行垃圾回收,进程中的一切被简单地删除。在UNIX 类的系统中,这像是多余的,但在内嵌式系统或多线程环境中这确实很必要。

  可以看出,perl 的面向对象概念是比较模糊的,而且一般的建议也是能不用它的面向对象机制,尽量不用使用。

  前面说过,python 作为面向对象的语言比较彻底,很多基本的内置类型就是面向对象的。如复数数值类型,num=3+0J,可以去它的实部num.real。他的类有分明的数据和方法说明,如我们定义前面perl 下STU 相同意义的类:

class STU:
def __init__(self, name, age): #self 表示对象本身的引用,实际是一隐藏参数
self.name=name
self.age=age
def prntinfo(self):
print name, age
#定义一个对象st
st=STU(“xiao rong”,22)
st.prntinfo()

  由python 的数据抽象和作用域规则确定数据的封装性,这一点和perl 相同。
  Python 类继承和C++类似,我们延续上面的例子:

class GSTU(STU):
def __init__(self, name, age, area):
STU.__init__(self, name, age)
self.area=area
def prntinfo(self):
STU.prntinfo(self)
print “research area:”+self.area

  因为python 不支持重名函数(函数重载),所以不支持多个构造函数(__init__),这不同C++和java 等面向对象语言。Python 允许定义所谓的虚构函数__del__(),尽管和perl 一样语言会进行自动废料收集,但__del__()允许对象在销毁时作一些更复杂的工作。

  九、 异常处理

  象python 支持异常处理机制的脚本语言里是少有的。这得益于python 设计的类似于java 的完全OO,即根本上面向对象。所有的异常是Exception 的字类,内置了诸如有语法错、算术运算异常、查找异常、环境异常、名字错、键盘中断、属性错、导入错、运行时错、类型错、断言错、引用错、内存用完、系统异常等基本异常类,涵盖大部分可能出现的异常并自动检查,可以很方便地进行异常控制。Python 的异常处理采取终止模型(termination model)。捕捉异常语法try…except 形式,用raise 产生一个异常如:

import sys
ARGV=sys.argv
a=eval(ARGV[1])
b=eval(ARGV[2]) #按整数读入参数a,b
try: #监察异常
print a/b #打印a/b
except ZeroDivisionError: #处理被0 除的异常
print “divided by zero!”
raise SyntaxError #引发一个语法错误的异常,这样将导致脚本终止

  一个语言要支持异常处理,首先应该定义各种异常,并有引发和捕获异常的]机制。一般异常的引发是由系统自动抛出,并且有默认的捕获和处理;程序员可以在程序设计时加入代码检查看是否由某种类型的异常抛出,并作相应处理;异常就近(在最近的一层里)处理,否则逐层传递,没有匹配的捕获,则程序出错终止。

  所以相比而言,perl 的异常处理机制很原始,类似C 语言,靠程序员检查,然后进行简单的处理,如输出错误信息,退出程序或警告。要想实现上例情形的perl 脚本可能是:

$a=;
$b=;
die(“divided by zero!”) if b=0; #如果除数为0 退出,别的情形可能用warn 警告
print a/b;

  如果有错误的预先检测,如上里的if 语句,则异常情况可以被特别处理;但如果不进行这种检测,则一旦异常出现,整个脚本被中断或异常将影响其它语句。

  针对这种情况,perl 里的eval 函数将有特别的用处。当脚本包含一个eval 函数调用时,一个perl 解释器新的请求将被建立,即运行了另外的一个解释器,并且这个解释器的执行阶段解释执行提供给eval 的语句块或表达式,一旦运行结束,该解释器运行终止。eval 函数的最大特点是运行时间执行perl 源代码,并且和主脚本代码的运行保持很大的独立性。所以eval 代码出现异常不会严重影响或中断整个脚本的继续执行。eval 可以接受单个表达式或语句块的输入,要特别注意的是当eval 一个语句块时,语句块是和其它部分一道被编译的,等到eval 是才执行,所以可能不具有你所期望的动态效果,如想动态的使用一个包:

$mod=$windows?’DBI:win32’:’DBI’; #根据操作系统的选择不同的模块
eval{use $mod;}; #在eval 块中打开模块$mod,但实际这种情况下$mod 是没有值的

  eval 的块和普通的语句块不同,它后面的“;”不可少,它是相对独立的解释单位。要实现上例的想法,可以用eval 表达式:

$mod=$windows?’DBI:win32’:’DBI’;
eval “use $mod”; #表达式的值是在运行中求得的,故此时$mod 有值

  eval 块中的异常信息将存储在系统变量$@中,可以通过访问该变量了解代码的运行状况。

  在perl 标准库里有为方便异常处理的一些模块如Carp 等,但错误的传递和逐层匹配处理能力较差,依赖于程序员的个人设计,语言不自动检查。

  十、 并发控制

  并发(concurrence) 是计算一个重要发展方向,也是克服硬件速度发展相对停滞、待处理问题规模复杂度不断增长的有效机制。很多操作系统通过多进程和多线程来实现并发处理。进程可以说是一个一个运行中的程序,有自己独立的内存空间和其他资源。而线程可以看成轻量级的进程,或进程的一个执行体,同一进程内的线程共享内存和资源以及一些全局环境和变量。进程和线程以及多任务的其它一些细节在这里不介绍了。Perl 和python 这两种脚本语言设计时都考虑了对支持进程和线程控制的支持。

  Perl 直接支持进程控制,可以在脚本运行时直接fork 出一个子进程,在UNIX系统这将运行系统调用fork,在win 系列系统创建的是伪进程(模拟子进程,生存期依赖它的创建进程)。fork 出的子进程直接运行进跟其后面的块中的代码。

  进程间的通信组要依赖与信号。Perl 支持POSIX 标准,定义了其中的大部分信号,信号和信号句柄存在字典中,这和通常的信号句柄系统大不一样。关键字时信号名字(比POSIX 有所简化,把SIG 省略了,如SIGINT 简化为INT),而相关值是当收到信号时应执行的动作函数的引用,如$SIG{INT}=sub {print “got SIGINT”;},表示该进程收到INT 信号时将打印出”got SIGINT”。另外一个进程间交换信息的方式是通过管道,与shell 中控制类似。

  Perl 中通过包Thread 支持多线程。创建线程时将让它执行的函数和参数传给它,如:

use Thread;
$t=new Thread(\&subprog, args); #创建一个新线程执行subprog 子程序

  Thread 包中有join 方法可以控制父线程等待子线程。线程的一些基本控制如获得名称,用户,父线程号,进程号在Thread 模块里有专门的函数,在此不细说。

  当线程间共享变量时可以通过条件变量或信号量(semaphore)来控制。通过lock 函数给变量加锁,有三个独立的函数可以去锁:cond_wait、cond_signal 和cond_broadcast 分别对应有线程在该变量上等待,发送信号,广播信号。信号量能够用来指示一些线程事件,所提供的信息是以数字出现,并且这些数字能相应的增加或减少,通常的方法是建立一个和可以共享的资源相关联的信号量。在perl 中如下建立信号量控制:

$sema= new Thread::Semaphore(0); #建立信号量并赋初值0,假定它和$val变量关联
$val=0;
sub sub1{
while(1){
if ($sema>0) {
$sema->down;
$val++;
wait(5);
if ($val>100) { last;}
$sema->up;
}
}
}
sub sub2{
while(1){
if ($sema>0) {
$sema->down;
$val–;
wait(10);
if ($val<0) { last;}
$sema->up;
}
}
}
$t1=new Thread \&sub1; #$t1 线程将执行sub1
$t2=new Thread \&sub2; #$t2 线程将执行sub2

  当然,因为一个信号可以终止脚本的运行,所以信号同样可以影响线程的运行,要建立用于信号操作的线程,必须引入Thread::Signal 模块,接下来的操作大体
和信号句柄类似。

  Python 通过os 模块来支持进程操作管理。通过fork 函数创建一个进程,wait或waitpid 函数等待子进程,通过信号或管道进行进程间通信。例如:

import os
while 1: #wit for a connection
if accepted:
pid=os.fork()
if not pid:
#do the child process
else:
os.waitpid(pid,os.WNOHANG) #非挂起等待pid 子进程结束

  在UNIX 或类似系统python 里的os.fork()函数只是操作系统的fork 的别名(windows 系统暂时不支持fork 函数),fork 出来的子进程执行其后的if 语句的一个分支(not pid 的分支)。通过os.kill 函数可以给子进程发送信号,而子进程要捕获信号必须通过signal 中的signal 函数安装信号句柄。下例说明了如何让子进程捕获SIGALRM 信号执行相应动作:

import os, signal, sys, time
def alarm_hdlr(signum, frame): #定义信号处理函数
print “wake up!”
sys.exit()
pid=os.fork() #创建子进程
if not pid:
signal.signal(signal.SIGALRM, alarm_hdlr) #安装信号句柄
while 1:
print “going to sleep…”
time.sleep(10)
print “now I’m up…”
else:
signal.alarm(5)
python 通过thread 模块来支持多线程,核心函数是start_new_thread(f,(args)),

  这个函数启动一个新线程,并让它执行以参数(args)执行f 函数。例如:

import thread, time
def dsptime(interval, prefix=’’): #函数dsptime 每隔一段时间显示一次当前时间
while 1:
print prefix, time.ctime(time.time())
time.sleep(interval)
thread.start_new_thread(dsptime, (10,’now time is:’))
while 1:
pass

  线程执行完函数子自动终止,或者可以调用sys.exit()强制其终止。子进程共享并访问父进程所有的全局对象和环境,python 由多种控制线程访问这些对象的方法。基本的方法是通过给对象加锁,例如:

import thread, time
cnt=0
cnt_lock=thread.allocate_lock() #定义了一个锁对象(只是一个协议),让它来控制cnt变量的访问
def inccnt():
while 1:
print “thread 1:excuting …”
if (cnt_lock.acquire(0)): #看是否能获能对锁变量的控制
global cnt
print “thread 1″,thread.get_ident(),”acquired lock…”
cnt+=1
time.sleep(0.01)
cnt_lock.release()
else:
print “thread 1 can’t get lock …”
time.sleep(1)
def dspcnt():
while 1:
if (cnt_lock.acquire(0)):
global cnt
print “thread 2″,thread.get_ident(),”acquired lock…”
print “thread 2 print”,cnt
time.sleep(0.01)
cnt_lock.release()
else:
print “thread 2 can’t get lock…”
time.sleep(1)
thread.start_new_thread(inccnt,())
thread.start_new_thread(dspcnt,())
while 1:
pass

  输出将显示两个线程的执行情况和对锁的控制。特别注意的是(在perl 和python 中的其它线程控制机制也有类似的问题),你可以完全不遵守锁对象的协议而直接访问到变量,那样将无法保证对变量使用的正确性。Python 还可以通过条件变量,信号量,队列和事件等对象来控制线程共享信息和通信。

  以上的几个小节,主要从语言的数据类型、控制结构、作用域、正则表达式处理、包和模块、面向对象、并发控制、与系统和环境交互等多个方面对 perl和python 进行了较粗略的比较分析,尽量突出两者之间的差别,并给以实例说明。大部分实例都是自己设计,少部分来自参考书,但都尽量在计算机上试验过。也对其中的一些特征和细节,如perl 的引用类型,python 的参数传递,进行了的思考,这些想法可能不是很正确。Perl 和python 各有它们广大的用户群,所以很难说孰优孰劣,而且因为自己对这两们语言的使用不多,有很多地方理解的不够深入和准确,请老师指正。

论坛徽章:
0
3 [报告]
发表于 2009-08-21 07:55 |只看该作者

回复 #1 spider007009 的帖子

论坛徽章:
0
4 [报告]
发表于 2009-08-21 09:41 |只看该作者
都很强大

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
5 [报告]
发表于 2009-08-21 13:00 |只看该作者
排版太差,谁给个摘要?

论坛徽章:
1
未羊
日期:2014-09-08 22:47:27
6 [报告]
发表于 2009-08-21 16:31 |只看该作者
有点长。

论坛徽章:
0
7 [报告]
发表于 2009-08-21 20:46 |只看该作者
顶楼主!!!

论坛徽章:
0
8 [报告]
发表于 2009-08-21 21:22 |只看该作者
#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”}


寫得很好

[ 本帖最后由 lokchungk 于 2009-8-21 21:25 编辑 ]

论坛徽章:
0
9 [报告]
发表于 2009-08-22 14:26 |只看该作者
python好读
perl写起来爽...
我的感觉.....
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP