Python FreshMan

★☆ Python新手的Blog ☆★
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

从C#到Python —— 1 变量和数据类型

Posted on 2010-02-27 11:41  闫小勇  阅读(14015)  评论(7编辑  收藏  举报

一切数据是对象,一切命名是引用”。

如果你能理解这句话,说明对Python的变量与数据类型已经有了不错的认识,那么我建议你直接跳到1.4节的总结部分,看看C#与Python在变量与数据类型方面的差异就可以。如果你还有疑惑,那么就请完整的读一下这一章吧。

好,让我们开始。

1.1 变量声明和定义

1.1.1  变量声明和定义

与C#不同,Python在使用变量之前无须定义它的类型,试着运行下面的例子:

1 i = 1
2  print i

从上边我们可以看到,变量 i 在使用前并不需要定义,但是必须声明以及初始化该变量。试着运行下面的例子:

1 i = 1
2  print i + j

上面的代码会产生一个异常:“NameError: name 'j' is not defined”,Python提示变量 j 没有定义。这点和BASIC等弱类型的语言不一样。在BASIC中,执行上述代码的时候不会产生异常,你可以在EXCEL的VBA开发环境里试一下,把print改为MsgBox就可以,结果会输出 1 。这说明Python并不是一种类似BASIC的弱类型语言。

另一方面,Python与C#有一个很大的差异就是在程序运行过程中,同一变量名可以(在不同阶段)代表不同类型的数据,看看下边的例子:

1 i = 1
2  print i,type(i),id(i)
3 i = 10000000000
4  print i,type(i),id(i)
5 i = 1.1
6  print i,type(i),id(i)

变量 i 的类型在程序执行过程中分别经历了int、long和float的变化,这和静态类型语言(如C等)有很大不同。静态语言只要一个变量获得了一个数据类型,它就会一直是这个类型,变量名代表的是用来存放数据的内存位置。而Python中使用的变量名只是各种数据及对象的引用,用id()获取的才是存放数据的内存位置,我们输入的1、10000000000和1.1三个数据均会保存在id()所指示的这些内存位置中,直到垃圾回收车把它拉走(在系统确定你不再使用它的时候)。这是动态语言的典型特征,它确定一个变量的类型是在给它赋值的时候。

另一方面,Python又是强类型的,试着运行下边的例子:

1 # -*- coding: utf-8 -*-
2  i = 10; j = 'ss'
3  print i+j
4  #正确的写法是print str(i)+j,输出10ss

会产生一个异常:“TypeError: unsupported operand type(s) for +: 'int' and 'str'”。在BASIC等弱类型的语言中,上边的例子会正常运行并返回(虽然有时候是不可预期的)结果。

所以,我们说Python既是一种动态类型语言,同时也是一种强类型的语言,这点是和C#不同的地方。对于Python的这种变量的声明、定义和使用方式,C#程序员可能要花一段时间去适应,不过相信你会很快就喜欢上它,因为它让事情变得更加简单(而且不会不安全)。而且,C# 4.0 已经开始用类似的方式定义和使用变量(通过在变量名前加关键字dynamic),如果你先学了Python,将能够更快的适应C# 4.0的动态编程特征。

1.1.2  变量的命名规则

Python与C#的变量(以及函数、类等其它标识符)的命名规则基本一样,同样对大小写敏感。不一样的地方是,Python中以下划线开始或者结束的标识符通常有特殊的意义。例如以一个下划线开始的标识符(如 _foo)不能用from module import *语句导入。前后均有两个下划线的标识符,如__init__,被特殊方法保留。前边有两个下划线的标识符,如__bar,被用来实现类私有属性,这个将在“类和面向对象编程”中再说。

最后,Python的关键字不能作为标识符(这个大家都知道),不过Python的关键字比C#要少得多,可以google一下,这里就不列出了。

1.1.3  常量

Python没有常量,如果你非要定义常量,可以引入const模块(我没用过,在C#中我也很少用常量)。

1.2 简单数据类型

Python 程序中的一切数据都是对象,包括自定义对象及基本数据类型。这点和C#一样,它们都是完全面向对象的语言,所以我想C#程序员会很容易理解Python的“一切数据是对象”这个口号。

与C#不同的是,Python不区分值类型和引用类型,你可以把所有的类型都理解为C#的引用类型(当然,它们的实现方式是不一样的,这里只是一个类比)。

Python内建的数据类型有20多种,其中有些不常用到,有些即将被合并。本文将主要介绍空类型、布尔类型、整型、浮点型和字符串、元组、列表、集合、字典等9种Python内置的数据类型。

在这里,我将前4种称为“简单数据类型”,将后5种称为“高级数据类型”,实际上Python语言本身没有这种叫法,这样分类是我自己设定的,主要是为了和C#中的相关概念对照方便,希望不要误导大家

1.2.1  空类型

空类型(None)表示该值是一个空对象,比如没有明确定义返回值的函数就返回None。空类型没有任何属性,经常被用做函数中可选参数的默认值。None的布尔值为假。

Python的None和C#中的可空类型Nullable<T>类似,比如C#可以定义Nullable<double> i = null,与Python的空类型类似,但实现原理和用途都不一样。

1.2.2 布尔类型

Python中用True和False来定义真假,你可以直接用 a = True或 a = False来定义一个布尔型变量。但在Python 2.6里,True、False以及None却都不是关键字,在Python 3.0里它们已经是关键字了,这个有点乱,我们可以不用管它,直接使用就OK了。

注意和C#不同的是,Python中True和False的首字母要大写。

最后一点,在C#中布尔类型和其他类型之间不存在标准的转换。但在Python中,None、任何数值类型中的0、空字符串''、空元组()、空列表[]、空字典{}都被当作False,其他对象均为True,这点和C++差不多,要提起注意。请思考一下,下面的Python代码会输出什么?

1 if 0:
2 print 'True'
3  else:
4 print 'False'

1.2.3 数值类型

Python拥有四种数值类型:整型,长整型,浮点类型以及复数类型。

整数类型(int)用来表示从-2147483648 到 2147483647之间的任意整数(在某些电脑系统上这个范围可能会更大,但绝不会比这个更小);长整数(long)可以表示任意范围的整数。实际上我们把Python的long和int理解为同一种类型就可以了,因为当一个整数超过int的范围后,Python会自动将其升级为长整型。所以,请忘掉C#中的byte、sbyte、short、ushort、int、uint、long和ulong吧,Python只有一种整数。

Python中只有64位双精度浮点数,与C#中的double类型相同(注意在Python中浮点数类型名字是float而不是double),Python不支持32位单精度的浮点数。

除了整数和实数,Python还提供了C#中不支持(当然可以通过自定义类来扩展)的一种数据类型:复数(complex)。复数使用一对浮点数表示,复数z的实部和虚部分别用z.real和z.imag访问。

在数值运算中,整数与浮点数运算的结果是浮点数,这就是所谓的“提升规则”,也就是“小”类型会被提升为“大”类型参与计算。这一点与C#是一样的,提升的顺序依次为:int、long、 float、complex。

作为数值类型的最后一个问题,C#程序员需要注意的是,Python没有内建decimal类型,但可以导入decimal模块用来完成与货币处理相关的计算。

 

1.3 高级数据类型

1.3.1  序列(字符串、列表和元组)

Python中的序列是由非负整数索引的对象的有序集合(真拗口,其实意思就是下标从0开始),它包括字符串、Unicode字符串、列表、元组、xrange对象以及缓冲区对象。后两种类型我们先不介绍,后边用到时再说明。

1.3.1.1  字符串类型

Python拥有两种字符串类型:标准字符串(str)是单字节字符序列,Unicode字符串(unicode)是双字节字符序列。

在Python中定义一个标准字符串(str)可以使用单引号、双引号甚至三引号,这使得Python输入文本比C#更方便。比如当str的内容中包含双引号时,就可以用单引号定义,反之亦然。当字符中有换行符等特殊字符时,可以直接使用三引号定义。这样就方便了很多,不用去记那些乱七八糟的转义字符。当然Python也支持转义字符,且含义和C#基本一样。不过既然有简单的东西用,谁还去自找麻烦呢?

下边是一个例子,来说明以上几点:

1 str1 = 'I am "Python"\n'
2 str2 = "I am 'Python'\r"
3 str3 = """
4 I'm "Python",
5 <a href="http://Csharp.com">you are C#</a>
6  """ #你可以把html代码之类的东西直接弄进来而不需要做特殊处理
7  print str1,str2,str3

 

在Python中定义一个Unicode字符串,需要在引号前面加上一个字符u,例如

1 # -*- coding: utf-8 -*-
2  print u'我是派森'

 

这点没有C#方便,因为C#字符串默认就是Unicode的,我想Python如果要改进,应该把两种字符串合二为一,这样可以为初学者减少很多麻烦(你看网上有多少帖子是在问Python怎么支持中文?根源都在这里)。同时注意,当使用utf-8编码时,非unicode字符中一个汉字的长度是3,而使用gb2312时是2,见下边代码:

1 # -*- coding: utf-8 -*-
2  unicode = u''
3 str = ''
4  print len(unicode),len(str)
5  #输出 1 3
6  
7  # -*- coding: gb2312 -*-
8  unicode = u''
9 str = ''
10  print len(unicode),len(str)
11  #输出 1 2

 

另外,Python没有C#中的字符类型,再短的文本也是字符串,这点稍微注意一下就可以,因为现在使用C#的也很少用char了吧?

最后,关于字符串的操作方法,基本上C#有的Python都有,可以看看Python手册之类的资料,我就不多说了。唯一提一点就是在Python中提取一个字符串的子串时,记得用“切片”语句(后边讲列表和元组时还会介绍),而不要再去找SubString了,见下边的例子:

1 # -*- coding: utf-8 -*-
2  str1 = u'我是派森'
3  print str1[2:4]
4  #输出 '派森'

 

1.3.1.2  列表(list)

Python中的列表(list)类似于C#中的可变数组(ArrayList),用于顺序存储结构。

列表用符号[]表示,中间的元素可以是任何类型(包括列表本身,以实现多维数组),元素之间用逗号分隔。取值或赋值的时候可以像C数组一样,按位置索引:

1 # -*- coding: utf-8 -*-
2  array = [1,2,3]
3  print array[0]
4  #输出 1
5  array[0] = 'a'
6  print array
7  #输出 ['a', 2, 3]

 

从上边的代码中你可能发现一个有趣的事情:在Python的列表中可以混合使用不同类型的数据,像['a', 2, 3]这样,不过我不建议你这样做,我觉得没什么好处(虽然个别场合下可能会比较方便)。

另外还可以看到,列表是可变的序列,也就是说我们可以在“原地”改变列表上某个位置所存储的对象(的值)。

C#中ArrayList支持的多数操作(如追加、插入、删除、清空、排序、反转、计数等),Python中的list也都支持,同时list也支持“切片”这种操作。切片指的是抽取序列的一部分,其形式为:list[start:end:step]。其抽取规则是:从 start开始,每次加上step,直到end为止。默认的step为1;当start 没有给出时,默认从list 的第一个元素开始;当end=-1 时表示list 的最后一个元素,依此类推。一些简单的例子见下边代码:

1 # -*- coding: utf-8 -*-
2  test = ['never', 1, 2, 'yes', 1, 'no', 'maybe']
3  print test[0:3] # 包括test[0],不包括test[3]
4  print test[0:6:2] # 包括test[0],不包括test[6],而且步长为2
5  print test[:-1] # 包括开始,不包括最后一个
6  print test[-3:] # 抽取最后3 个

 

字符串、列表、元组都支持切片操作,这个很方便,应该学会熟练使用它。

最后,list是Python中最基础的数据结构,你可以把它当作链表、堆栈或队列来使用,效率还不错。Python中没有固定长度数组,如果你确实很在意性能,可以导入array模块来创建一个C风格的数组,它的效率很高,这里就不详细介绍了。

1.3.1.3  元组(tuple)

元组与列表非常相似,它是用()而不是[]括起来的序列。元组比列表的速度更快,但元组是一个不可变的序列,也就是与str一样,无法在原位改变它的值。除此之外,其他属性与列表基本一致。

元组定义的方法与列表类似,不过在定义只包含一个元素的元组时,注意在后边加一个逗号。请体会以下几句语句的差异:

1 # -*- coding: utf-8 -*-
2  test = [0] #列表可以这样定义
3  print type(test) #输出<type 'list'>
4  test = [0,] #也可以这样定义
5  print type(test) #输出<type 'list'>
6  test = (0,) #元组可以这样定义
7  print type(test) #输出<type 'tuple'>
8  test = (0) #但不能这样定义,Python会认为它是一个括号表达式
9  print type(test) #输出<type 'int'>
10  test = 0, #也可以省略括号,但要注意与C的逗号表达式不同
11  print type(test) #输出<type 'tuple'>

 

利用元组的这个特性,可以简化Python变量的初始化过程,如:

1 x,y,z=1,2,3

还可以很简单地进行数据交换。比如:

1 a = 1
2 b = 2
3 a,b = b,a

以上这类语句在Python中被广泛应用于变量交换、函数传值等应用,因此Python的解释器在不断对其进行优化,现在已经具备了相当高的效率。所以以上代码在Python 2.5以后的版本中,比tmp = a; a = b; b = tmp这种常规语句更快。

 

1.3.2  集合(set)

Python中的set和C#中的集合(collection)不是一个概念,这是翻译的问题。Python中的集合是指无序的、不重复的元素集,类似数学中的集合概念,可对其进行交、并、差、补等逻辑运算。

常见集合的语法为:s = set(['a', 'b', 'c'])。不过set在Python 3.0中发生了较大的变化,创建一个集合的语法变成了:s = {1,2,3},用花括弧的方法,与后边要提到的dict类似。

如果在set中传入重复元素,集合会自动将其合并。这个特性非常有用,比如去除列表里大量的重复元素,用set解决效率很高,示例如下:

1 # -*- coding: utf-8 -*-
2  a = [11,22,33,44,11,22,11,11,22,22,33,33,33]
3 b = set(a)
4  print b
5  #输出 set([33, 11, 44, 22])

另一个例子,找出两个list里面相同的元素(集合求交,其它类推),代码如下:

1 # -*- coding: utf-8 -*-
2  a = ["11","22","33"]
3 b = ["11","33"]
4 c = set(a)&set(b)
5  print c
6  #输出 set(['11', '33'])

想想你如果自己实现这个算法会怎么写?然后可以找两个大一点的列表,比比和set实现的效率,你就会有体会了。以后在程序里多用set吧。
目前C#的Collections中好像还没有Set,但是C++ STL里是有的,不知道C#为什么不实现这个有趣的东西。

 

1.3.3  字典(dict)

用过C#中Collections的人对Hashtable应该不会陌生,Python里的哈希表就是字典(dict)了。与set类似,字典是一种无序存储结构,它包括关键字(key)和关键字对应的值(value)。

C#程序员需要了解的就是,在Python中dict是一种内置的数据类型,定义方式为:dictionary = {key:value},当有多个键值对时,使用逗号进行分割。

字典里的关键字为不可变类型,如字符串、整数、只包含不可变对象的元组,列表等不能作为关键字。字典中一个键只能与一个值关联,对于同一个键,后添加的值会覆盖之前的值。

学过数据结构的人对字典的散列查找效率应该都有认识,所以我建议在可能的情况下尽量多用字典,其它的就不多写了。关于Python中dict类型(以及list、tuple、set)提供的主要方法,可以参考专门介绍Python的各种书籍,大多会提供一个详细的方法列表。

 

1.4  小结

本章讨论了Python中变量和数据类型的使用方法,要点如下:

(1) Python是一种动态的强类型语言,在使用变量之前无须定义其类型,但是必须声明和初始化;
(2) “一切命名是引用”,Python中变量名是对象的引用,同一变量名可以在程序运行的不同阶段代表不同类型的数据;
(3) “一切数据是对象”,Python的所有数据类型都是对象,(相较C#)具有一致的使用方法;
(4) “把问题想得更简单一点”,Python的数值类型可以说只有两种:整形和浮点,忘掉C#里的各种数值类型吧;
(5) 注意区别str和unicode,Python的字符串类型有时候会让人发晕,请试着习惯它,另外不要忘了“切片”这个好工具。
(6) 多使用list, tuple, set 和 dict这几种“很Python”的数据类型,它们分别用[]、()、([])和{}定义。

好了,本章的内容我觉得已经介绍的有点过多了,我们先说到这一步,类型的操作符等内容我将在下一章《运算符、表达式和流程控制》里介绍。至于对象的深拷贝、浅拷贝等,为了不上来就把大家搞迷糊,我就不再写了。这方面的资料也很多,文末列了一些推荐读物,希望对你有所帮助。


最后,在这里发一个短小的声明:从今天开始,《从C#到Python》改为一周更新一篇。因为我3月1日就开学了,马上要上课、备课和带毕业设计,所以只能利用周末来写博。为了能尽快写完这个连载,我对章节也进行了一些调整,去掉了原计划中的I/O一章,因为这部分很简单,写出来可能导致章节间不均衡。这样就只剩下了4章(表达式、函数、类和模块),我计划在1个月之内写完。如果想速成Python,推荐你去看看假正经哥哥(这名叫的!)整理的一个《Python快速参考指南》,可谓是“一图在手,江山我有”啊:)

 

进一步阅读的参考:

[1]关于动态语言、静态语言、强类型语言、弱类型语言的概念,可参考《深入 Python :Dive Into Python 中文版》的2.2.1.“ Python 和其他编程语言数据类型的比较”;

[2]如果你还没接触到C# 4.0,可以看看《c# 4.0新特性一览》,我觉得C#越变越像Python了。

[3]关于Python中各种数据类型的详细介绍,请参考《可爱的Python》一书中的PCS101“内建数据类型”和《Python 精要参考(第二版)》的第三章“类型和对象”,本章的写作也主要参考的是这两本书,在此特向原作者(及译者)致谢。

[4]关于类型与对象的高级内容,推荐你看看《Python核心编程(中文第二版)》的第4章“Python对象”,这个有点难,好多东西我还没看懂,呵呵。