作为万年Perl 党表示最近开始学Python 了,下面会记录一下学习中Python 和Perl 的对比,如果你也是一个Perl 用户,看过了也会对Python 有一个大致的印象吧。
事实上,写着写着我发现如果你是一名Python 用户,看完后也会对Perl 有一个大致的了解 _(:з)∠)_
基本数据类型
1. Perl 中的标量
a. Perl 中的标量在Python 中对应为数字类型和字符串类型
Perl 的标量是字符串还是数值会根据上下文自动判断,但是Python 不会这样做。
下面的代码是你在Perl 中习以为常的操作
my ($string,$number) = ("1",); $number = $string + "1"; # $string 和"1" 都被解释为数值 say $number; $string = $number.1; # $number 和1 都被解释为字符串 say $string;
但是Python 中你必须显式的指定数据类型
string = "1" number = int (string) + 1 # 必须显式转换为数值类型 print (number) string = str (number) + "1" # 同样必须显式转换为字符串 print (string)
b. Perl 的标量不支持下标运算,但是Python 的字符串可以
Python 中你可以很方便的用下标索引字符串(而且和Perl 中的列表一样也支持用负数做反向索引)
s = "A string" print (s[2:])但是Perl 的标量就无法这样操作,相同的操作必须用列表切片完成
$_ = "A string"; say ((split //)[2..split //]);
c. Perl 中的heredoc 式标量赋值在Python 中可以使用三引号运算符完成
@_ = <<END; Everthing is in the array END say "@_";下面是等价的Python 代码,注意第一行是紧跟三引号运算符之后的
string = '''Everthing is in the array ''' print (string)
2. Perl 中的列表
a. Perl 中的列表Python 中对应为列表和元组
下面是Python 中倒序排序一个列表的操作
my_list = ["Hello","Python"] my_list.append ("World") my_list += ["!"] print (my_list) my_list.sort (); my_list.reverse (); for string in my_list: print (string)对应的Perl 同性质操作的版本
@_ = qw/Hello Perl/; push @_,'World'; @_ = (@_,'!'); print "@_\n"; @_ = reverse sort @_; say for @_;
b. 常用的splice 操作在Python 中可以用过index () 方法和del 操作完成
下面是Perl 中常用的splice 操作
@_ = qw/Hello Perl !/; splice @_,2,"World"; # 插入元素 say "@_"; my @removed = splice @_,1,2; # 提取一段元素并在原列表删除 print "@_\n@removed\n";Python 中对应的操作
my_list = ["Hello","Python","!"] my_list.insert (2,"World") # 插入元素 print (my_list) removed = my_list[1:2] # 提取一段元素 del my_list[1:2] # 并在原列表删除 print (my_list) print (removed)
注意:Python 中元组和列表的区别
元组创建后是只读的,但是列表随时都可以被修改。
3. Perl 中的哈希
a. Perl 中的哈希Python 中对应为字典和集合
一个Perl 哈希按照键值升序遍历的操作如下
my %hash = ( "first" => 1,"second" => 2,"third" => 3 ); say "$_ => $hash{$_}" for sort keys %hash;Python 中代码如下(注意定义变为花括号)
my_hash = { "first" : 1,"second" : 2,"third" : 3 } items = sorted (my_hash.items (),key=lambda e:e[1],reverse=False) for item in items: print (item[0],':',item[1])
注意:Python 中集合与字典的不同之处
(a) 仅包含键,但是没有值
(b) set 可变,但是frozenset 不可变(如其名)
4. Python 强类型的判断和赋值
一个非常大的不同之处:Perl 中赋值号默认的行为是拷贝,但是Python 中赋值号会创建一个对象的引用。
另外,因为和弱类型的Perl 不同,Python 是一门强类型语言,所以也有判断对象类型的必要。
lst = [1,3] lst2 = lst # lst2 是lst 的一个引用 print (lst) lst2[1] = 2.5 # 所以修改lst2 也会引起lst 的变化 print (lst) lst3 = copy.deepcopy (lst) # lst3 是lst 的一个深拷贝 lst3[1] = 2 print (lst) # 修改lst3 不会引起lst 的变化 # 因为Python 是强类型语言,所以需要类型判断操作 if lst == lst2: # 如果s 和s2 值相同 print ("lst equal to lst2") if lst is lst2: # 如果s 和s2 是指向同一个对象的引用 print ("lst and lst2 is the same") if type (lst) is type (lst3): # 如果s 和s3 类型相同 print ("lst and lst3 has same type")下面是与之完全等价的Perl 代码,对比一下你就会很快了解其中的差别
my @lst = (1..3); my $lst2 = \@lst; # lst2 是lst 的一个引用 say "@$lst2"; $lst2->[1] = 2.5; # 所以修改lst2 也会引起lst 的变化 say "@$lst2"; my @lst3 = @lst; # lst3 是lst 的一个深拷贝 $lst3[1] = 2; say "@lst"; # 修改lst3 不会引起lst 的变化 =pod 因为Perl 是弱类型语言,所以不需要类型判断操作 但是Perl 中仍然可以做这样的操作——虽然没什么意义 =cut say "lst equal to lst2" if @lst == @$lst2; # 如果s 和s2 值相同 say "lst and lst2 is the same" if \@lst == $lst2; # 如果s 和s2 是指向同一个对象的引用 say "lst and lst3 has same type" if ref \@lst eq ref \@lst3; # 如果s 和s3 类型相同
5. Perl 中的“上下文”
上下文的判断几乎已经成了一名Perl 用户不可或缺的第六感,一个显而易见的例子是当你定义一个列表
my @lst = (1..3);当你循环遍历的时候,你深知把for 循环
say for @lst; # 列表上下文替换为While 循环
say while @lst; # 标量上下文究竟会得到什么灾难性的后果。
但是在Python 中你不必担心这个:Python 对于数据类型的解释很明确,不会因为上下文环境不同而改变,当你定义一个列表
lst = [1,3]
之后,你永远不必担心在什么特殊的上下文中lst 会被Python 解释为数值3。
面向对象编程
1. 几点明显的异同
Perl 和Python 的面向对象给人的感觉是后者明显比前者更加规范,下面是自己感受最明显的几点异同。
a. 真的是面向对象么?
1). Perl 中的“面向对象”更像是“面向过程”的文字游戏:类通过包实现、方法通过包中定义的函数实现、类的继承和方法的重载通过@ISA 列表查找循序实现、私有方法通过指向匿名函数的my 引用实现(因为my变量是本文件可见的)、运算符重载通过指向函数的引用实现、对象中成员变量的访问通过bless 到类名的引用实现……这样如果说有好处,那就是编程可以非常灵活——你可以用一个父类的多个子类共同继承出一个类(例如从哺乳动物中人类和猫类继承出一种新生物),Perl 完全对这种行为不在意,只要你确信这种近亲结婚的方式真的是你需要达到的目的。当然坏处显而易见——你可以轻而易举把代码写的糟糕透顶。所以Perl 的“面向对象”给人的感觉只是“面向过程”的另一种玩法——面向过程的本质没变,但是面向对象的效果达到了。
2). Python 中的“面向对象”比Perl 要严谨和规范许多,非常类似于Java,如果你熟悉Java 或者C++,那么你会很好理解Python 的面向对象编程。
b. 包和类
1). Perl 中两者完全等价,一个包就是一个类(pm 是Perl 模块的意思,但是它又被叫做包,而包就是类的意思 ← ←)。
2). Python 中一个包可以包含多个模块,一个模块可以包含多个类。
c. 静态方法
Perl 和Python 中静态方法都是第一个参数不是类的引用的方法,但是稍有不同:
1). Perl 中静态方法第一个参数是类名,可以通过bless 新的引用到类名来操作对象类型(例如你在构造方法里做的那样)。
2). Python 中静态方法完全无法操作对象类型。
d. 私有方法:
1). Perl 中的私有方法通过my 变量只有当前文件可见的性质,用保存匿名函数的my 引用来达到“私有”的目的(“面向对象”的文字游戏)。
2). Python 中吧以“__”开头的方法都当作私有方法,通过方法名会变成"_类名__方法名" 的形式来避免其他类调用该方法,但是你仍然可以通过手动变换后的方法名直接调用私有方法。
e. 方法的传参:
1). Perl 中一般将散列的引用bless 到类名,所以传参可以十分灵活,如果构造函数允许,参数个数和位置根本无关紧要,但是随之造成的问题就是可能引发混乱。
2). Python 中方法声明无法把无默认值的参数放在有默认值的参数后面,但是因为实参可以通过给出参数名手动显式指定,所以次序也可以无关紧要。
f. 运算符重载:
1). Perl 通过use overload 模块指定方法的引用来达到重载运算符的目的。
2). Python 中通过一组特殊名称的方法来重载运算符。
g. 父类方法重载:
1). Perl 中通过@ISA 列表的搜索顺序来达到重载父类方法的目的(子类的同名方法会被优先搜索到),并且可以显式SUPER 伪类访问被覆盖的基类方法(就如你经常在析构方法中做的一样)。
2). Python 的重载更加正式,形式非常类似于C++。
h. 继承:
1). Perl 的继承只是操作了@ISA 列表,子类中没有的方法会在@ISA 中寻找方法名,因此这种行为得到的结果和面向对象编程的继承相同。UNIVERSAL 是所有类的祖先类,提供了isa 方法用来测试继承关系。
2). Python 的继承类似于C++,显式指定了要继承的父类,object 类是所有类的祖先类,提供issubclass 方法用来测试继承关系。
2. 一个演示异同的例子
下面的两个例子都会有相同的输出,演示了Perl 和Python 中类的构造、析构、公有方法、私有方法、运算符重载、继承、父类方法重载等。
下面是预期的输出
=My name's Lucy,2 years old. Adoption me please. +I am hungry offen. -My name's Leia,1 years old. My host is iSpeller. +I hate milk but my host give me offen. -My name's Lucy,2 years old. My host is iSpeller. +I hate milk but my host give me offen.
--------------------------------------------------------
下面是你熟悉的Perl
#!/usr/bin/perl # ======================== # filename: main.pm # main 类,演示了: # 类的实例化 # ======================= package main; use warnings; use strict; use 5.010; use Dog; use Pet_Dog; push @INC,'.'; # 一条叫Lucy 的汪星人 my $lucy = Dog->new (name => 'Lucy',age => 2); $lucy->say_hello; $lucy->my_secret; # 第一条宠物汪,默认为1 岁的leia my $pet_leia = Pet_Dog->new (host => 'iSpeller'); $pet_leia->say_hello; $pet_leia->my_secret; # 纳入第二个宠物汪 # 调用了Pet 类运算符重载函数 my $pet_lucy = $lucy + "iSpeller"; $pet_lucy->say_hello; $pet_lucy->my_secret; 1;
# ======================== # filename: Dog.pm # Pet 类,演示了: # 构造、析构方法 # 公有、私有方法 # 重载 # ======================== package Dog; use strict; use warnings; use 5.010; use overload '+' => \&meet; # 重载加号运算符 # 构造方法 # 是静态方法,第一个参数为类名 sub new { my $type = shift; my $class = ref $type || $type; # 如有用户实例变量则覆盖默认属性 my $self = { name => 'Leia',age => 1,is_pet => 0,@_ }; bless $self,$class; return $self; } # 析构方法 # 是虚方法,第一个参数为类的引用 sub DESTROY { my $self = shift; # 调用父类析构方法 $self->SUPER::DESTROY if $self->can ('SUPER::DESTROY'); } # 公有方法 sub say_hello { my $self = shift; print '=' if $self->isa ("UNIVERSAL"); # UNIVERSAL 类是所有类的祖先类 printf "My name's %s,%d years old. %s.\n",$self->{name},$self->{age},$self->{is_pet} ? "I am a pet dot" : "Adoption me please"; } # 私有方法 my $say_secret = sub { my $self = shift; say '+',$self->{is_pet} ? "I hate milk but my host give me offen." : "I am hungry offen."; }; # 私有方法只能在本文件内由其他方法访问(my $say_secret) sub my_secret { my $self = shift; $self->$say_secret; } # 重载加号运算符,返回成为宠物后的自身 sub meet { my $self = shift; my @property = %$self; my $new = Pet_Dog->new (@property,host => shift); return $new; } 1;
# ======================== # filename: Pet_Dog.pm # Pet_Dog 类,继承自Dog 类,演示了: # 继承、父类方法的重载 # ======================= package Pet_Dog; use strict; use warnings; use 5.010; use base qw/Dog/; # 继承自Dog 类 sub new { # 调用父类的构造方法 # 因为shift 得到的是子类的类名,所以不需要重新bless my $self = Dog::new (shift,host => "none",@_,is_pet => 1); return $self; } # 重载父类的say_hello (虚)方法 sub say_hello { my $self = shift; print '-' if $self->isa ("Dog"); # 继承关系测试 printf "My name's %s,%d years old. My host is %s.\n",$self->{host}; } 1;
--------------------------------------------------------
下面是完全等价的Python
#!/usr/bin/python3 # filename: main.py # ======================== # Dog 类,演示了: # 构造、析构方法 # 公有、私有方法 # 重载 # ======================== class Dog: # 构造方法 # 是私有方法,因为方法名以"__" 开头 def __init__ (self,name = "Leia",age = 1,is_pet = 0): # 如有用户实例变量则覆盖默认属性 self.name = name self.age = age self.is_pet = is_pet # 析构方法 # 静态方法,不会操作实例类型 # 类似Perl,第一个参数不是引用,所以你无法通过第一个参数来引用实例变量 @staticmethod def __del__ (): pass # 公有方法 def say_hello (self): if issubclass (Dog,object): print ("=",end='') print ("My name's %s,%d years old. %s." % (self.name,self.age,# Python 中没有三目运算符,可以用下面的形式替代 "I am a pet dog" if self.is_pet else "Adoption me please")) # 私有方法 def __say_secret (self): print ("+%s." % ("I hate milk but my host give me offen" if self.is_pet else "I am hungry offen")) # 私有方法只能在本类内由其他方法访问 def my_secret (self): self.__say_secret () # 重载加号运算符为和对方恋爱,返回成为女朋友后的自身 def __add__ (self,other): new = Pet_Dog (self.name,other) return (new) # ======================== # Pet_Dog 类,继承自Dog 类,演示了: # 继承、父类方法的重载 # ======================== class Pet_Dog (Dog): # 调用父类的构造方法 # 之后初始化子类变量 def __init__ (self,is_pet = 1,host = "none"): Dog.__init__ (self,name,age,is_pet) self.host = host # 重载父类的say_hello (虚)方法 def say_hello (self): if issubclass (Pet_Dog,Dog): print ("-",%d years old. My host is %s." % (self.name,self.host)) ''' 程序开始,Python 的类型不允许在定义之前使用 然而Python 似乎又不区分声明和定义 演示了类的实例化 ''' # 一条叫Lucy 的汪星人 lucy = Dog ("Lucy",2) lucy.say_hello () lucy.my_secret () # 第一条宠物汪,默认为1 岁的leia pet_dog_leia = Pet_Dog (host = "iSpeller"); pet_dog_leia.say_hello () pet_dog_leia.my_secret () # 纳入第二宠物汪 # 调用了Pet 类运算符重载函数 pet_dog_lucy = lucy + "iSpeller" pet_dog_lucy.say_hello () pet_dog_lucy.my_secret ()