第一章 快速改造:基础知识
1.1 安装Python (略······)
安装Python教程网上能找到很多,这里我不想手打了......
1.2 交互式解释器
当启动Python的时候,会出现和下面相似的提示:
Python 2.7.11 (v2.7.11:6d1b6a68f775, Dec 5 2015, 20:40:30) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
注:不同的版本的提示和错误信息可能会有所不同。
进入Python的交互式解释器之后输入下面的命令查看它是否正常的工作:
>>> print "Hello,world!"
按下回车后,会得到下面的输出:
Hello,world!
1.3 算法是什么
首先解释一下什么是计算机程序设计。简单地说,它就是告诉计算机要做什么。计算机可以做很多的事情,但是不太擅长自主思考,程序员要像给小孩喂饭一样告诉它具体的细节,并且使用计算机能够理解的语言——算法。“算法”不过是“步骤”或者“食谱”的另外一种说法——对于如何做某事的一份详细的表述。比如:
SPAM拌SPAM、SPAM、鸡蛋和SPAM:
首先,拿一些SPAM;
然后加入一些SPAM、SPAM和鸡蛋;
如果喜欢吃特别辣的SPAM,再多加点SPAM;
煮到熟为止——每10分钟检查一次。
这个食谱可能不是很有趣,但是它的组成结构还是有些讲究的。它包括一系列按顺序执行的指令。有些指令可以直接完成(“拿一些SPAM”),有些则需要考虑特定的条件(“如果需要特别辣的SPAM”),还有一些则必须重复次数(“每10分钟检查一次”)。
食谱和算法都包括一些材料(对象,物品),以及指令(语句)。本例中,“SPAM”和“鸡蛋”就是要素,指令则包括添加SPAM、按照给定的时间烹调,等等。
1.4 数字和表达式
交互式Python解释器可以当作非常强大的计算器使用,如以下的例子:
>>> 256868 + 684681
941549
以上是非常普通的功能。在绝大多数情况下,常用算术运算符的功能和计算器的相同。这里有个潜在的陷阱,就是整数除法(在3.0版本之前的Python是这样的)。
>>> 1 / 2
0
从上面的例子可以看出,一个整数(无小数部分的数)被另一个整数除,计算结果的小数部分被截除了,只留下整数部分。有些时候,这个功能很有用,但通常人们只需要普通的除法。有两个有效的解决方案:要么用实数(包含小数点的数),而不是整数进行运算,要么让Python改变除法的执行方式。
实数在Python中被称为浮点数(Float,或者Float-point Number),如果参与除法的两个数中有一个数为浮点数,则运算结果亦为浮点数:
>>> 1.0 / 2.0
0.5
>>> 1 / 2.0
0.5
>>> 1 / 2.
0.5
如果希望Python只执行普通的除法,那么可以在程序前加上一下语句,或者直接在解释器里面执行它:
>>> from __future__ import division
还有另一个方法,如果通过命令行运行Python,可以使用命令开关-Qnew
。使用上述两种方法,就可以只执行普通的除法运算:
>>> 1/2
0.5
当然,单斜线不再用作前面提到的整除了,但是Python提供了另外一个用于实现整除的操作符——双斜线:
>>> 1 // 2
0
就算是浮点数,双斜线也会执行整除:
>>> 1.0 // 2.0
0.0
现在,已经了解基本的算数运算符了(加、减、乘、除)。除此之外,还有一个非常有用的运算符:
>>> 1 % 2
1
这是取余(模除)运算符——x % y
的结果为x
除以y
的余数。下面是另外一些例子:
>>> 10 / 3
3
>>> 10 % 3
1
>>> 9 / 3
3
>>> 9 % 3 0
>>> 2.75 % 0.5
0.25
这里10/3
得3
是因为结果被向下取整了。而3x3=9
,所以相应的余数就是1
了。在计算9/3
时,结果就是3
,没有小数部分可供截除,因此,余数就是0
了。如果要进行一些类似文章前面菜谱所述“每10分钟”检查一次的操作,那么,取余运算就非常有用了,直接检查时间10%
的结果是否为0
即可。从上述最后一个例子可以看到,取余运算符对浮点数也同样适用。
最后一个运算符就是幂(乘方)运算符:
>>> 2 ** 3
8
>>> -3 ** 2
-9
>>> (-3) ** 2
9
注:幂运算符比取反(一元减运算符)的优先级要高,所以-3**2
等同于-(3**2)
。如果想计算(-3)**2
,就需要显示说明。
1.4.1 长整数
Python可以处理非常大的整数:
>>> 100000000000000000000
100000000000000000000L
可以看到数字后面自动加上了一个L
普通的整数不能大于2 147 483 647
(也不能小于-2 147 483 648
),如果需要更大的数,可以使用长整数。长整数的书写方法和普通整数一样,但是结尾有个L
。(理论上,用小写的l
也可以,但是小写的l看起来太像1
,所以建议不要用小写。)
在前面的例子中,Python把整数转换为了长整数,但是我们自己也可以完成:
>>> 100000000000000000000L
100000000000000000000L
当然,这只是在不能处理大数的旧版Python中很有用。
也可以对这些庞大的数字进行运算,例如:
>>> 165165846835413545L * 2654684351365435434L + 1846846746843
438463188973992663445213017233300373L
正如所看到的那样,长整数和普通整数可以混合使用。在绝大多数情况下,无需担心长整数和整数的区别,除非需要进行类型检查。
1.4.2 十六进制和八进制
在Python中,十六进制数应该像下面这样书写:
>>> 0xAF
175
而八进制数则是:
>>> 010
8
十六进制和八进制数的首位数字都是0
1.5 变量
变量(variable)是另外一个需要熟知的概念。Python中的变量很好理解。变量基本上就是代表(或者引用)某值的名字。举例来说,如果希望用名字x
代表3
,只需要执行下面的语句即可:
>>> x = 3
这样的操作成为赋值(assignment),数值3被赋值给了变量x
。或者说:将变量x绑定到了值(或者对象)3
上面。在变量被赋值之后,就可以在表达式中使用变量。
>>> x * 2
6
注意,在使用变量之前,需要对其赋值。毕竟不代表任何值的变量没有什么意义。
注:变量名可以包括字母、数字和下划线(`)。变量不能以数字开头,所以
Plan9是合法变量名,而
9Plan`不是。_
1.6 语句
到现在为止,我们一直都在讲述表达式,也就是“食谱”的“材料”。那么,语句(也就是指令)是什么呢?
刚才已经介绍了两类语句:print
语句和赋值语句。那么语句和表达式之间有什么区别呢?表达式就是某件事情,而语句是做某件事情(即告诉计算机做什么)。比如2*2
是4
,而print
2*2则是打印
4`。那么区别在哪呢?毕竟,它们的行为非常相似。请看下面的例子:
>>> 2 * 2
4
>>> print 2 * 2
4
如果在交互式解释器中执行上述两行代码,结果是一样的。但这只是因为交互式解释器总是把所有表达式的值打印出来而已(都使用了相同的repr
函数对结果进行呈现,详细参见1.11.3节)。一般情况下,Python并不会那样做。在程序中编写类似2*2
这样的表达式并不能做什么有趣的事情(它只计算了2*2
的结果,但是结果并不会在某处保存或显示给用户,它对运算本身之外的东西没有任何的副作用)。另一方面,编写print 2*2
则会打印出4
。
语句和表达式之间的区别在赋值时会表现的更加明显一些。因为语句不是表达式,所以没有值可供交互式解释器打印出来:
>>> x = 3
>>>
可以看到,下面立刻出现了新的提示符。但是,有些东西已经变化了,x
现在绑定给了值3
。
这也是能定义语句的一般性特征:它们改变了事务。比如,赋值语句改变了变量,print
语句改变了屏幕显示的内容。
赋值语句可能是任何计算机程序设计语言中最重要的语句类型,尽管现在还难以说清它们的重要性。变量就像临时的“存储器”(就像烹饪食谱中的锅碗瓢盆一样。但是值并没有保存在变量中——它们保存在计算机内存的深处,被变量引用。随着本书内容的深入,你会对此越来越清楚:多个变量可以引用同一个值。),它的强大之处就在于,在操作变量的时候并不需要知道它们存储了什么值。比如,即使不知道x和y的值到底是多少,也会知道x*y
的结果就是x
和y
的乘积。所以,可以在程序中通过多种方法来使用变量,而不需要知道在程序运行的时候,最终存储(或引用)的值到底是什么。
1.7 获取用户输入
我们在编写程序的时候,并不需要知道变量的具体值。当然,解释器最终还是得知道变量的值。可是,它怎么会知道我们都不知道的事呢?解释器只知道我们告诉它的内容,对吧?不一定。
事实上,我们通常编写程序让别人用,我们无法预测用户会给程序提供什么样的值。那么,看看非常有用的input
函数吧:
>>> input("You this year many big?: ")
You this year many big?: 18
18
在这里,交互式解释器执行了第一行的input(...)
语句。它打印出了字符串"You this year many big?:"
,并以此作为新的提示符,输入18
然后按下回车。input
语句的结果值就是我输入的数字,它自动在最后一行被打印出来。这个例子确实不太有用,但是请接着看下面的内容:
>>> x = input("x: ")
x: 16
>>> y = input("y: ")
y: 31
>>> print x * y
496
Python提示符(>>>
)后面的语句可以算作一个完整程序的组成部分了,输入的值(16
和31
)有用户提供,而程序就会打印出输入的两个数的乘积496
。在编写程序的时候,并不需要知道用户输入的数是多少,对吧?
注:这种做法非常有用,因为你可以将程序存为单独的文件,以便让其他用户可以直接执行。
管窥:if语句
if
语句可以让程序在给定条件为真的情况下执行某些操作(执行另外的语句)。一类条件是使用相等运算符==
进行相等性测试。是两个等号,一个等号是用来赋值的。
可以简单地把这个条件放在if后面,然后用冒号将其和后面的语句隔开:
>>> if 1 == 2:
print "One equals two"
...
>>> if 1 == 1:
print "One equals one"
...
One equals one
>>>
可以看到,当条件为假(False
)的时候,什么都没有发生;当条件为真(True
)的时候,后面的语句(本例中为print
语句)被执行。注意,如果在交互式解释器内使用if
语句,需要按两次回车,if
语句才能执行。(第五章会对其原因进行说明。)
所以,如果变量time
绑定到当前时间的分钟数上,那么可以使用下面的语句检查是不是“到了整点”。
>>> if time % 60 == 0:
print "On the hour!"
1.8 函数
在1.4节中曾经介绍过使用幂运算符(**
)来计算乘方。事实上,可以用一个函数来代替这个运算符,这个函数就是pow:
>>> 2 ** 3
8
>>> pow(2, 3)
8
函数就像小型程序一样,可以用来实现特定的功能。Python有很多函数,它们能做很奇妙的事情。你也可以自己定义函数(后面会对此展开讲述)。因此,我们通常把pow
等标准函数称为内建函数。
上例中我使用函数的方式叫做调用函数。你可以给它提供参数(本例中的2和3)。它会返回值给用户。因为它返回了值,函数调用也可以简单看作另外一类表达式,就像在本章前面讨论的算数表达式一样(如果忽略了返回值,函数调用也可以看成语句)。事实上,可以结合使用函数调用和运算符来创建更复杂的表达式:
>>> 10 + pow(2, 3 * 5) / 3.0
10932.666666666666
还有很多像这样的内建函数可以用于数值表达式。比如使用abs
函数可以得到数的绝对值,round
函数则会把浮点数四舍五入为最接近的整数值:
>>> abs(-10) 10
>>> round(1.0 / 2.0)
1.0
注意最后两个表达式的区别。整数除法总是会截除结果的小数部分,而round
函数则会将结果四舍五入为最接近的整数。但是如果想将给定的数值向下取整为某个特定的整数呢?比如一个人的年龄是32.9岁——但是想把它取整为32,因为她还没到33岁。Python有实现这样功能的函数(称为floor
),但是不能直接使用它。与其他很多有用的函数一样,你可以在某个模块中找到floor
函数。
1.9 模块
可以把模块想象成导入到Python以增强其功能的扩展。需要使用特殊的命令import
来导入模块。前面内容提到floor
函数就在名为math
的模块中:
>>> import math
>>> math.floor(32.9)
32.0
注意它是怎么起作用的:用import导入了模块,然后按照“模块.函数”的格式使用这个模块的函数。
如果想把年龄转换为整数(32
)而不是浮点数(32.0
),可以使用int
函数。(int
函数/类型把参数转换成整数时会自动向下取整,所以在转换过程中,math.floor
是多余的,可以直接使用int(32.9)
)
>>> int(math.floor(32.9))
32
注:还有类似的函数可以将输入数转换为其他类型(比如long
和float
)。事实上,他并不完全是普通的函数——它们是类型对象(type object
)。后面,我将会对类型进行详述。与floor
相对的函数是ceil
(ceiling的简写),可以将给定的数值转换成为大于或等于它的最小整数。
在确定自己不会导入多个同名函数(从不同模块导入)的情况下,你可能希望不要在每次调用函数的时候都写上模块的名字。那么,可以使用import
命令的另外一种形式:
>>> from math import sqrt
>>> sqrt(9)
3.0
在使用了from 模块 import 函数
这种形式的import
命令之后,就可以直接使用函数,而不需要模块名作为前缀。
注:事实上,可以用变量来引用函数(或者Python之中大多数的对象)。比如,通过 foo = math.sqrt
进行赋值,然后就可以使用foo
来计算平方根了:foo(4)
的结果为2.0
。
1.9.1 cmath
和复数
sqrt
函数用于计算一个数的平方根。看看如果给它一个负数作为参数会如何:
>>> from math import sqrt >>> sqrt(-1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: math domain error
或者,在其他平台会有以下结果:
>>> sqrt(-1)
nan
注:nan
是一个特殊值的简写,意思是“not a number” (非数值)。
这也情有可原,不能求负数的平方根。真的不可以么?其实可以:负数的平方根是虚数(这是标准的数学概念,如果感觉有些绕不过弯来,跳过即可)。那么为什么不能使用sqrt
?因为它只能处理浮点数,而虚数(以及复数,即实数和虚数之和)是完全不同的。因此,它们由另一个叫做cmath
(即complex math,复数)的模块来处理。
>>> import cmath
>>> cmath.sqrt(-1)
1j
注意,我在这里并没有使用from...import...
语句。因为一旦使用了这个语句,就没法使用普通的sqrt
函数了。这类命名冲突可能很隐蔽,因此,除非真的需要from
这个形式的模块导入语句,否则应该坚持使用普通的import
。
1j
是个虚数,虚数均已j
(或者J
)结尾,就像长整数使用L
一样。我们在这里不深究复数的理论,只举最后一个例子,来看一下如何使用复数:
>>> (1 + 3j) * (9 + 4j)
(-3+31j)
可以看到,Python语言本身就提供了对复数的支持。
注:Python中没有单独的虚数类型。它们被看做实数部分为0的复数。
1.9.2 回到 __future__
有传言说Guido van Rossum(Python之父)拥有一架时光机,因为在人们要求增加语言新特性的时候,这个特性通常都已经实现了。当然,我等凡夫俗子是不允许进入这架时光机的。但是Guido很善良,他将时光机的一部分以 __future__
这个充满魔力的模块的形式融入了Python。通过它可以导入那些在未来会成为标准Python组成部分的新特性。你已经在1.4节见识过这个模块了,而在本书余下的部分,你还将与它不期而遇。
1.10 保存并执行程序
交互式解释器是Python的强项之一,它让人们能够实时检验解决方案并且用这门语言做一些实验。如果想知道如何使用某些语句,那么就试试看吧!但是,在交互式解释器里面输入的一切都会在它退出的时候丢失。而我们真正想要的是编写自己和他人都能运行的程序。在本节中,将会介绍如何实现这一点。
首先,需要一个文本编辑器,最好是专门用来编程的。如果使用Microsoft Word这样的编辑器(我并不推荐这么做),那么得保证代码是以纯文本形式保存的。如果已经在用IDLE,那么,很幸运:用File→New Windows方式创建一个新的编辑窗口即可。这样,另外一个窗口就出现了,没有交互式提示符,很好!
先输入以下内容:
print "Hello, world!"
现在选择File→Save保存程序(其实就是纯文本文件)。要确保将程序保存在一个以后能找到的地方。你应该专门建立一个存放Python项目的目录,还要为自己的程序文件起个有意义的名字。比如hello.py
。文件名以.py
结尾是很重要的。
然后就可以使用Edit→Run或者按下Ctrl+F5键来运行程序了(如果没有用IDLE,请查看下一节有关如何在命令提示符下运行程序的内容)。
你会发现"Hello, world!"
在解释器窗口内打印出来了,这就是想要的结果。解释器提示符没了(不同的版本会有所差异),但是可以按下回车键将它找回了(在解释器窗口按回车键)。
接下来,我们对上述脚本进行扩展,如下例所示:
name = raw_input("What is your name? ")
print "Hello, " + name + "!"
注:不用管input
和raw_input
的区别,稍后,我会进行介绍的。
如果运行这个程序(记得先保存),应该会在解释器窗口中看到下面的提示:
What is your name?
输入你的名字(比如XuHoo
),然后按下回车键。你将会看到如下内容:
Hello, XuHoo!
1.10.1 通过命令提示符运行Python脚本
事实上,运行程序的方法有很多。首先,假定打开了DOS窗口或者UNIX中的Shell提示符,并且进入了某个包含Python可执行文件(在Windows中是python.exe
,而UNIX中则是python
)的目录,或者包含了这个可执行文件的目录已经放置在环境变量PATH
中了(仅适用于Windows)。同时假设,上一节的脚本文件(hello.py
)也在当前目录中。那么,可以在Windows中使用以下命令执行来脚本:
C:\> python hello.py
或者在UNIX下:
$ python hello.py
可以看到,命令是一样的,仅仅是系统提示符不同。
注:如果不想跟什么环境变量打交道,可以直接指定Python解释器的完整路径。在Windows中,可以通过以下命令完成操作:
# 根据你的Python版本更改版本号
C:\> C:\Python27\python hello.py
1.10.2 让脚本像普通程序一样运行
有些时候希望像运行其他程序(比如Web浏览器、文本编辑器)一样运行Python程序(也叫做脚本),而不需要显式使用Python解释器。在UNIX中有个标准的实现方法:在脚本首行前面加上#!
(叫做pound bang或者shebang),在其后加上用于解释脚本的程序的绝对路径(在这里,用于解释代码的程序是Python)。即使不太明白其中的原理,如果希望自己的代码能够在UNIX下顺利执行,那么只要把下面的内容放在脚本的首行即可:
#!/usr/bin/env python
不管Python的二进制文件在哪里,程序都会自动执行。
注:在某些操作系统中,如果安装了最新版的Python,同时旧版的Python仍然存在(因为某些系统程序需要它,所以不能把它卸载),那么在这种情况下,/usr/bin/env
技巧就不好用了,因为旧版的Python可能会运行程序。因此需要找到新版本Python可执行文件(可能叫做python或python2)的具体位置,然后在pound bang行中使用完整的路径,如下例所示:
#!/usr/bin/python2
具体的路径会因系统而异。
在实际运行脚本之前,必须让脚本文件具有可执行的属性(UNIX系统):
$ chmod a+x hello.py
现在就能这样运行了(假设当前目录包含在路径中):
$ hello.py
注意如果上述操作不起作用的话,试试./hello.py
。即当前的目录(.
)并不是执行路径的一部分,这样的操作也能够成功。
在Windows系统中,让代码像普通程序一样运行的关键在于后缀名.py
。加入双击上一节保存好的hello.py
文件,如果Python安装正确,那么,一个DOS窗口就会出现,里面有"What is your name?"
提示。
然而,像这样运行程序可能会碰到一个问题:程序运行完毕,窗口也跟着关闭了。也就是说,输入了名字以后,还没来得及看结果,程序窗口就已经关闭了。试着改改代码,在最后加上以下这行代码:
raw_input("Press <enter>")
这样,在运行程序并且输入名字之后,将会出现一个包含以下内容的DOS窗口:
What is your name? XuHoo
Hello, XuHoo!
Press <enter>
用户按下回车键以后,窗口就会关闭(因为程序运行结束了)。作为后面内容的预告,现在请你把文件名改为hello.pyw
(这是Windows专用的文件类型),像刚才一样双击。你会发现什么都没有!怎么会这样?在本书后面的内容将会告诉你答案。
1.10.3 注释
井号(#
)在Python中有些特殊。在代码中输入它的时候,它右边的一切内容都会被忽略(这也是之前Python解释器不会被/usr/bin/env
行“卡住”的原因了)。比如:
# 打印圆的周长:
print 2 * pi * radius
这里的第一行称为注释。注释是非常有用的,即为了让别人能够更容易理解程序,也为了额你自己回头再看旧代码。据说程序员的第一条戒律就是“汝应注释”(Thou Shalt Comment)(尽管很多刻薄的程序员的座右铭是“如果难写,就该难读”)。程序员应该确保注释说的都是重要的事情,而不是重复代码中显而易见的内容。无用的、多余的注释还不如没有。例如,下例中的注释就不好:
# 获得用户名:
user_name = raw_input("What is your name? ")
即使没有注释,也应该让代码本身易于理解。幸好,Python是一门出色的语言,它能帮助程序员编写易于理解的程序。
1.11 字符串
那么,raw_input
函数和"Hello, " + name + "!"
这些内容到底是什么意思?放下raw_input
函数暂且不表,先来说"Hello"
这个部分。
本章的第一个程序是这样的,很简单:
print "Hello, world!"
在编程类图书中,习惯上都会以这样一个程序作为开篇——问题是我还没有真正解释它是怎么工作的。前面已经介绍了print
语句的基本知识(随后我会介绍更多相关的内容),但是"Hello, world!"
是什么呢?是字符串(即“一串字符”)。字符串在几乎所有真实可用的Python程序中都会存在,并且有多种用法,其实最主要的用法就是表示一些文本,比如这个感叹句"Hello, world!"
。
1.11.1 单引号字符串和转义引号
字符串是值,就像数字一样:
>>> "Hello, world!"
'Hello, world!'
但是,本例中有一个地方可能会让人觉得吃惊:当Python打印出字符串的时候,是用单引号括起来的,但是我们在程序中用的是双引号。这有什么却别吗?事实上,并没有区别。
>>> 'Hello, world!'
'Hello, world!'
这里也用了单引号,结果是一样的。那么,为什么两个都可以用呢?因为在某些情况下,它们会排上用场:
>>> "Let's go!"
"Let's go!"
>>> '"Hello, world!" she said'
'"Hello, world!" she said'
在上面的代码中,第一段字符串包含了单引号,这时候就不能用单引号将整个字符串括起来了。如果这么做,解释器会提示错误:
>>> 'Let's go!'
File "<stdin>", line 1
'Let's go!'
^ SyntaxError: invalid syntax
在这里字符串为'Let'
,Python并不知道如何处理后面的s
(也就是该行余下的内容)。
在第二个字符串中,句子包含了双引号。所以,出于之前所述的原因,就需要用单引号把字符串括起来了。或者,并不一定要这样做,尽管这样做很直观。另外一个选择就是:使用反斜线(\
)对字符串中的引号进行转义:
>>> 'Let\'s go!'
"Let's go!"
Python会明白中间的单引号是字符串中的一个字符,而不是字符串的结束标记(即便如此,Python也会在打印字符串的时候在最外层使用双印号)。有的人可能已经猜到,对双引号也可以使用相同的方式转义:
>>> "\"Hello, world!\" she said"
'"Hello, world!" she said'
像这样转义引号十分有用,有些时候甚至还是必需的。例如,如果希望打印出一个包含单双引号的字符串,不用反斜线的话能怎么办呢?比如字符'Let\'s say "Hello, world!"'
?
注:在本章后面的内容中,将会介绍通过使用长字符串和原始字符串(两者可以联合使用)来减少绝大多数反斜线的使用。
1.11.2 拼接字符串
继续探究刚才的例子,我们可以通过另外一种方式输出同样的字符串:
>>> "Let's say" '"Hello, world!"'
'Let\'s say"Hello, world!"'
我只是用一个接着另一个的方式写了两个字符串,Python就会自动拼接它们(将它们合为一个字符串)。这种机制用得不多,有时却非常有用。不过,它们只是在同时写下两个字符串时才有效,而且要一个紧接着另一个。否则会出现下面的错误:
>>> x = "Hello, "
>>> y = "world!"
>>> x y
File "<stdin>", line 1 x y
^ SyntaxError: invalid syntax
换句话说,这仅仅是书写字符串的一种特殊方法,并不是拼接字符串的一般方法。那么,该怎样拼接字符串呢?就像进行加法运算一样:
>>> "Hello, " + "world!"
'Hello, world!'
>>> x = "Hello, "
>>> y = "world!"
>>> x + y
'Hello, world!'
1.11.3 字符串表示,str和repr
通过前面的例子读者们可能注意到了,所有通过Python打印的字符串还是被引号括起来的。这是因为Python打印值的时候会保持该值在Python代码中的状态,而不是你希望用户所看到的状态。如果使用print
语句,结果就不一样了:
>>> "Hello, world!"
'Hello, world!'
>>> 1000000L
1000000L
>>> print "Hello, world!"
Hello, world!
>>> print 1000000L
1000000
可以看到,长整型数1 000 000L
被转换成了数字1 000 000
,而且在显示给用户的时候也如此。但是当你想知道一个变量的值是多少时,可能会对它是整型还是长整型感兴趣。
我们在这里讨论的实际上是值被转换为字符串的两种机制。可以通过以下两个函数来使用这两种机制:一种是通过str
函数,它会把值转换为合理形式的字符串,以便用户可以理解;另一种是通过repr
函数,它会创建一个字符串,以合法的Python表达式的形式来表示值(事实上,str
和int
、long
一样,是一种类型。而repr
仅仅是函数)。下面是一些例子:
>>> print repr("Hello, world!")
'Hello, world!'
>>> print repr(1000000L)
1000000L
>>> print str("Hello, world!")
Hello, world!
>>> print str(1000000L)
1000000
repr(x)
也可以写作`x`
实现(注意,`
是反引号,而不是单引号)。如果希望打印出一个包含数字的句子,那么反引号就很有用了。比如:
>>> temp = 42
>>> print "The temperature is " + temp
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: cannot concatenate 'str' and 'int' objects
>>> print "The temperature is " + `temp`
The temperature is 42
注:在Python3.0中,已经不再使用反引号了。因此,即使在旧的代码中看到了反引号,你也应该坚持使用repr
。
第一个print
语句并不能工作,那是因为不可以将字符串和数字进行相加。而第二个则可以正常工作,因为我已经通过反引号将temp
的值转换为字符串"42"
了。(当然,也可以通过repr
,也可以得到同样的结果。但是,使用反引号可能更清楚一些。事实上,本例中也可以使用str
。)
简而言之,str
、repr
和反引号是将Python值转换为字符串的三种方法。函数str
让字符串更易于阅读,而repr
(和反引号)则把结果字符串转换为合法的Python表达式。
1.11.4 input和raw_input的比较
相信读者已经知道"Hello, " + name + "!"
是什么意思了,那么,raw_input
函数怎么用呢?input
函数不够好吗?让我们试一下。在另外一个脚本文件中输入下面的语句:
name = input("What is your name? ")
print "Hello, " + name + "!"
看起来这是一个完全合法的程序。但是马上你就会看到,这样是不可行的。尝试运行该程序:
What is your name? XuHoo
Traceback (most recent call last):
File "p.py", line 2, in <module>
name = input("What is your name? ")
File "<string>", line 1, in <module>
NameError: name 'XuHoo' is not defined
问题在于input
会假设用户输入的是合法的Python表达式(或多或少有些与repr
函数相反的意思)。如果以字符串作为输入的名字,程序运行是没有问题的:
What is your name?
"XuHoo" Hello, XuHoo!
然而,要求用户带着引号输入他们的名字有点过分,因此,这就需要使用raw_input
函数,它会把所有的输入当做原始数据(raw data),然后将其放入字符串中:
>>> input("Enter a number: ")
Enter a number: 3
3
>>> raw_input("Enter a number: ")
Enter a number: 3
'3'
除非对input
有特别的需要,否则应该尽可能使用raw_input
函数。
1.11.5 长字符串、原始字符串和Unicode
在结束本章之前,还会介绍另外两种书写字符串的方法。在需要长达多行的字符串或者包含多种特殊字符的字符串的时候,这些候选的字符串语法就会非常有用。
1.长字符串
如果需要写一个非常非常长的字符串,它需要跨多行,那么,可以使用三个引号代替普通引号:
print '''This is a very long string.
It continues here.
And it's not over yet.
"Hello, world!"
Still here.'''
也可以使用三个双引号,如"""This is a very long string"""
。注意,因为这种与众不同的引用方式,你可以在字符串之中同时使用单引号和双引号,而不需要使用反斜线进行转义。
注:普通字符串也可以跨行。如果一行之中最后一个字符是反斜线,那么,换行符本身就“转义”了,也就是被忽略了,例如:
>>> print "Hello, \
... world!" Hello, world!
这个用法也适用于表达式和语句:
>>> 1 + 2 + \
... 4 + 5
12
>>> print \
... "Hello, world!"
Hello, world!
2.原始字符串
原始字符串对于反斜线并不会特殊对待。在某些情况下这个特性是很有用的(尤其是在书写正则表达式时候,原始字符串就会特别有用)。在普通字符串中,反斜线有特殊的作用:它会转义,让你在字符串中加入通常情况下不能直接加入的内容。例如,换行符可以写为\n
,并可放于字符串中,如下所示:
>>> print "Hello, \nworld!"
Hello,
world!
这看起来不错,但是有时候,这并非是想要的结果。如果希望在字符串中包含反斜线再加上n怎么办呢?例如,可能需要像DOS路径"C:\nowhere"
这样的字符:
>>> path = "C:\nowhere"
>>> path 'C:\nowhere'
这看起来是正确的,但是,在打印该字符串的时候就会发现问题了:
>>> print path
C:
owhere
这并不是期望的结果,那么该怎么办呢?我可以使用反斜线对其本身进行转义:
>>> print "C:\\nowhere"
C:\nowhere
这看起来不错,但是对于长路径,那么可能需要很多反斜线:
>>> path = "C:\\Program Files\\fnord\\foo\\bar\\frozz\\bozz"
在这样的情况下,原始字符串就派上用场了。原始字符串不会把反斜线当做特殊字符。在原始字符串中输入的每个字符都会与书写的方式保持一致:
>>> print r"C:\nowhere" C:\nowhere
>>> print r"C:\Program Files\fnord\foo\bar\frozz\bozz"
C:\Program Files\fnord\foo\bar\frozz\bozz
可以看到,原始字符串以r
开头。看起来可以在原始字符串中放入任何字符,而这种说法也是基本正确的。当然,我们也要像平常一样对引号进行转义,但是,最后输出的字符串包含了转义所用的反斜线:
>>> print r'Let\'s go!'
Let\'s go!
不能在原始字符串结尾输入反斜线。换句话说,原始字符串最后的一个字符不能是反斜线,除非你对反斜线进行转义(用于转义的反斜线也会成为字符串的一部分)。参照上一个范例,这是一个显而易见的结论。如果最后一个字符(位于结束引号前的那个)是反斜线,Python就不清楚是否应该结束字符串:
>>> print r"This is illegal\"
File "<stdin>", line 1
print r"This is illegal\"
^ SyntaxError: EOL while scanning string literal
好了,这样还是合理的,但是如果希望原始字符只以一个反斜线作为结尾符的话,那该怎么办呢?(例如,DOS路径的最后一个字符有可能是反斜线)好,本节已经告诉了你很多解决此问题的技巧,但本质上就是把反斜线单独作为一个字符串来处理。以下就是一种简单的做法:
>>> print r"C:\Program Files\foo\bar" "\\"
C:\Program Files\foo\bar\
注:你可以在原始字符串中同时使用单双引号,甚至三引号字符串也可以
3. Unicode
字符串
字符串常量的最后一种类型就是Unicode
字符串(或者称为Unicode
对象,与字符串并不是同一个类型)。如果你不知道什么是Unicode
,那么,可能不需要了解这些内容(如果希望了解更多的信息,可以访问Unicode
的网站。Python中的普通字符串在内部是以18位的ASCII码形成存储的,而Unicode
字符串则存储为16位的Unicode
字符,这样就能够表示更多的字符集了,包括世界上大多数语言的特殊字符。本节不会详细讲述Unicode
字符串,仅举一下的例子来说明:
>>> u"Hello, world!"
u'Hello, world!'
可以看到,Unicode
字符串使用u
前缀,就像原始字符串使用r
一样。
注:在Python 3.0中,所有字符串都是Unicode
字符串。
1.12 小结
本章讲了非常多的内容。在继续下一章之前,先来看一下本章都学到了什么。
√ 算法。算法是对如何完成一项任务的详尽描述。实际上,在编写程序的时候,就是要使用计算机能够理解的语言(如Python)来描述算法。这类对机器友好的描述就叫做程序,程序主要包含表达式和语句。
√ 表达式。表达式是计算机程序的组成部分,它用于表示值。距离来说,2+2是表达式,表示数值4。简单的表达式就是通过使用运算符(如+或%)和函数(如pow)对字面值(比如2或者"Hello")进行处理而构建起来的。通过把简单的表达式联合起来可以建立更加复杂的表达式(如(2+2)*(3-1))。表达式也可以包含变量。
√ 变量。变量是一个名字,它表示某个值。通过x=2这样的赋值可以为变量赋予新的值。赋值也是一类语句。
√ 语句。语句是告诉计算机做某些事情的指令。它可能涉及改变变量(通过赋值)、向屏幕打印内容(如print "Hello, world!")、导入模块或者许多其他操作。
√ 函数。Python中的函数就像数学中的函数:它们可以带有参数,并且返回值(第六章会介绍如何编写自定义函数)。
√ 模块。模块是一些对Python功能的扩展,它可以被导入到Python中。例如,math模块提供了很多有用的数学函数。
√ 程序。本章之前的内容已经介绍过编写、保存和运行Python程序的实际操作了。
√ 字符串。字符串非常简单——就是文本片段,不过,还有很多与字符串相关的知识需要了解。在本章中,你已经看到很多种书写字符串的方法。第三章将会介绍更多字符串的使用方式。
1.12.1 本章的新函数
abs(number) 返回数字的绝对值
cmath.sqrt(number) 返回平方根,也可以应用于负数
float(object) 将字符串和数字转换为浮点数
help() 提供交互式帮助
input(prompt) 获取用户输入
int(object) 将字符串和数字转换为整数
long(object) 将字符串和数字转换为长整型数
math.ceil(number) 返回数的上入整数,返回值的类型为浮点数
math.floor(number) 返回数的下入整数,返回值的类型为浮点数
math.sqrt(number) 返回平方根,不适用于负数
pow(x, y[, z]) 返回x的y次方幂(所得结果对z取模)
raw_input(prompt) 获取用户输入,结果被看做原始字符
repr(object) 返回值的字符串表示形式
round(number[, ndigits) 根据给定的精度对数字进行四舍五入
str(object) 将值转换为字符串
1.12.2 接下来学什么
表达式的基础知识已经讲解完毕,接下来要探讨更高级一点的内容:数据结构。你将学习到如何不再直接和简单的值(如数字)打交道,而是把它们集中起来处理,存储在更加复杂的结构中,如列表(list)和字典(dictionary)。另外,我们还将深入了解字符串。在第五章中,将会介绍更多关于语句的知识。之后,编写漂亮的程序就手到擒来了。