python基础教程(第3版)
Python程序员会熟悉文件对象的方法: readline ()、write()、read()等。Python库支持文件和文件类对象。Socket对象则不提供类似的接口,您或许会觉得这样不是很方便。然而Python的确提供了一个makefile ( )函数来生成供您使用的文件类对象。
Python 的socket模块实际上定义了4种可能出现的异常:
- 与一般I/O 和通信问题有关的socket.error;
- 与查询地址信息有关的socket.gaierror;
- 与其他地址错误有关的socket.herror(和C语言中的h_errno相关
- 与在一个socket上调用settimeout ()后,处理超时有关的socket.timeout(需要Python2.3或更高版本)。
快速上手:基础知识
1.1 交互式解释器
Python 3.5.0 (default, Dec 5 2015, 15:03:35)
[GCC 4.2.1 Compatible Apple LLVM 7.0.0 (clang-700.1.76)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
如果你熟悉其他计算机语言,可能习惯了在每行末尾都加上分号。在Python中无需这样做,
因为在Python中,一行就是一行。如果你愿意,也可加上分号,但不会有任何影响(除非后面还
有其他代码),况且大家通常都不这样做。
1.2 算法是什么
1.3 数和表达式
交互式Python解释器可用作功能强大的计算器
除法运算的结果为小数,即浮点数(float或floating-point number)。
>>> 1 / 2
0.5
>>> 1 / 1
1.0
如果你想丢弃小数部分,即执行整除运算,可使用双斜杠。
>>> 1 // 2
0
>>> 1 // 1
1
>>> 5.0 // 2.4
2.0
在较旧的Python版本中,对整数执行常规除法运算的结果与使用双斜杠类似。
如果你使用的是Python 2.x,要对整数执行常规除法运算,可在程序开头添加如下语句(稍后介绍如何编写完
整的程序),也可直接在交互式解释器中执行这条语句:>> from __future__ import division
另外,从命令行运行较旧的Python版本时,还可使用命令行开关-Qnew。1.8.2节将更详尽地
介绍__future__。
关于版本的问题,“future”目的是把下一个版本的特性导入到当前版本,这样我们就可以在当前版本中测试一些新版本的特性,从而使得python未来版本的迁移更加容易。
future语句必须在靠近模块开头的位置出现。只有以下内容可以放在future语句之前。
1、模块的文档字符串
2、注释
3、空行
4、其他future语句
即x % y 等价于x - ((x // y) * y)。
这种运算符甚至可用于负数,但可能不那么好理解。
>>> 10 % 3
1
>>> 10 % -3
-2
>>> -10 % 3
2
>>> -10 % -3
-1
C语言遵循了尽量让商尽量靠近0的原则,两个异号的数取余之后的结果取决于分子的符号。
然而,在Python中,取余的计算公式与别的语言并没有什么区别:r=a-n*[a//n]
这里r是余数,a是被除数,n是除数。
不过在“a//n”这一步,当a是负数的时候,会向下取整,也就是说向负无穷方向取整。这也就得到:
两个异号的数取余之后的结果取决于分母的符号。
C跟分子;
python跟分母
请注意,乘方运算符的优先级比求负(单目减)高,因此-32等价于-(32)。如果你要计
算的是(-3)**2,必须明确指出。
十六进制数、八进制数和二进制数分别以下面的方式表示:
>>> 0xAF
175
>>> 010
8
>>> 0b1011010010
722
这些表示法都以0打头。
1.4 变量
使用Python变量前必须给它赋值,因为Python变量没有默认值。
1.5 语句
1.6 获取用户输入
你编写的程序可能供他人使用,无法预测用户会向程序提供什么样的值。我们来看看很有用
的函数input(稍后将更详细地介绍函数)。
>>> input("The meaning of life: ")
The meaning of life: 42
'42'
这里在交互式解释器中执行了第一行(input(...)),它打印字符串"The meaning of life:",
提示用户输入相应的信息。我输入42并按回车。这个数被input(以文本或字符串的方式)返回,
并在最后一行被自动打印出来。通过使用int将字符串转换为整数,可编写一个更有趣的示例:
>>> x = input("x: ")
x: 34
>>> y = input("y: ")
y: 42
>>> print(int(x) * int(y))
1428
- 先睹为快:if语句
>>> if 1 == 2: print('One equals two')
...
>>> if 1 == 1: print('One equals one')
...
One equals one
>>>
需要注意的另一点是,在交互式解释器中输入if语句后,需要按两次回车键才能执行它 (其中的原因将在第5章介绍)。
1.7 函数
1.3节使用了乘方运算符(**)来执行幂运算。实际上,可不使用这个运算符,而使用函数pow。
>>> 2 ** 3
8
>>> pow(2, 3)
8
有多个内置函数可用于编写数值表达式。例如,abs计算绝对值,round将浮点数圆整为与之
最接近的整数。
>>> abs(-10)
10
>>> 2 // 3
0
>>> round(2 / 3)
1.0
请注意最后两个表达式的差别。整数总是向下圆整,而round圆整到最接近的整数,并在两
个整数一样近时圆整到偶数。如果要将给定的数向下圆整,该如何做呢?例如,你知道某人的年
龄为32.9,并想将这个值向下圆整为32,因为他还没有满33岁。Python提供了完成这种任务的函
数floor,但你不能直接使用它,因为像众多很有用的函数一样,它也包含在模块中。
1.8 模块
可将模块视为扩展,通过将其导入可以扩展Python功能。要导入模块,可使用特殊命令
import。前一节提及的函数floor包含在模块math中。
>>> import math
>>> math.floor(32.9)
32
请注意其中的工作原理:我们使用import导入模块,再以module.function的方式使用模块
中的函数。就这里执行的操作而言,也可像前面处理input的返回值那样,将这个数字转换为
整数。
>>> int(32.9)
32
还有一些类似的函数,可用于转换类型,如str和float。实际上,它们并不是函数,而
是类。类将在本书后面更详细地介绍。
模块math还包含其他几个很有用的函数。例如,ceil与floor相反,返回大于或等于给定数
的最小整数。
>>> math.ceil(32.3)
33
>>> math.ceil(32)
32
如果确定不会从不同模块导入多个同名函数,你可能不想每次调用函数时都指定模块名。在
这种情况下,可使用命令import的如下变种:
>>> from math import sqrt
>>> sqrt(9)
3.0
通过使用命令import的变种from module import function,可在调用函数时不指定模块前缀。
事实上,可使用变量来引用函数(以及其他大部分Python元素)。执行赋值语句foo =
math.sqrt后,就可使用foo来计算平方根。例如,foo(4)的结果为2.0。
1.8.1 cmath 和复数
函数sqrt用于计算平方根。下面来看看向它提供一个负数的情况:
>>> from math import sqrt
>>> sqrt(-1)
Traceback (most recent call last):
...
ValueError: math domain error
在有些平台上,结果如下:
>>> sqrt(-1)
nan
nan具有特殊含义,指的是“非数值”(not a number)
如果我们坚持将值域限定为实数,并使用其近似的浮点数实现,就无法计算负数的平方根。
负数的平方根为虚数,而由实部和虚部组成的数为复数。Python标准库提供了一个专门用于处理
复数的模块。
>>> import cmath
>>> cmath.sqrt(-1)
1j
注意到这里没有使用from ... import ...。如果使用了这种import命令,将无法使用常规函
数sqrt。类似这样的名称冲突很隐蔽,因此除非必须使用from版的import命令,否则应坚持使用
常规版import命令。
1j是个虚数,虚数都以j(或J)结尾。复数算术运算都基于如下定义:-1的平方根为1j。这
里不深入探讨这个主题,只举一个例子来结束对复数的讨论:
>>> (1 + 3j) * (9 + 4j)
(-3 + 31j)
从这个示例可知,Python本身提供了对复数的支持。
Python没有专门表示虚数的类型,而将虚数视为实部为零的复数。
1.8.2 回到未来
据说Python之父Guido van Rossum有一台时光机,因为这样的情况出现了多次:大家要求
Python提供某项功能时,却发现这项功能早已实现。当然,并非什么人都能进入这台时光机,不
过Guido很体贴,通过神奇模块__future__让Python具备了时光机的部分功能。对于Python当前不
支持,但未来将成为标准组成部分的功能,你可从这个模块进行导入。这一点你在1.3节已经见
识过,本书后面也将经常遇到这个模块。
1.9 保存并执行程序
首先,你需要一个文本编辑器——最好是专门用于编程的(不推荐使用Microsoft Word之类
的软件,但如果你使用的是这样的软件,务必以纯文本的方式保存代码)。如果你使用的是IDLE,
那就太幸运了。在这种情况下,只需选择菜单File→New File。
1.9.1 从命令提示符运行 Python 脚本
在Windows中使用如下命令来执行这个脚本:
C:>python hello.py
在UNIX系统中,可使用如下命令:
$ python hello.py
如你所见,命令是一样的,只是系统提示符不同。
1.9.2 让脚本像普通程序一样
在有些情况下,你希望能够像执行其他程序(如Web浏览器或文本编辑器)一样执行Python
脚本,而无需显式地使用Python解释器。UNIX提供了实现这种目标的标准方式:让脚本的第一
行以字符序列#!(称为pound bang或shebang)开始,并在它后面指定用于对脚本进行解释的程序
(这里是Python)的绝对路径。即便你对这一点不太明白,只需将下面的代码作为脚本的第一行,
就可在UNIX中轻松运行脚本:
#!/usr/bin/env python
不管Python库位于什么地方,这都将让你能够像运行普通程序一样运行脚本。如果你安装了
多个版本的Python,可用更具体的可执行文件名(如python3)替换python。
要像普通程序一样运行脚本,还必须将其变成可执行的:
$ chmod a+x hello.py
现在,可以像下面这样来运行它(假定当前目录包含在执行路径中):
$ hello.py
如果这不管用,请尝试使用./hello.py,这在当前目录(.)未包含在执行路径中时也管用(负
责的系统管理员会告诉你执行路径是什么)。
如果你愿意,可对文件进行重命名并删除扩展名.py,使其看起来更像普通程序。
如果双击会如何呢
在Windows中,扩展名.py是让脚本像普通程序一样的关键所在。请尝试双击前一节保存的文
件hello.py。
1.9.3 注释
在Python中,井号(#)比较特殊:在代码中,井号后面到行尾的所有内容都将被忽略。(这
也是Python解释器未被前面的/usr/bin/env卡住的原因所在。)
1.10 字符串
1.10.1 单引号字符串以及对引号转义
第二个字符串包含双引号,因此必须使用单引号将整个字符串括起,原因和前面一样。实
际上,并非必须这样做(这样做只是出于方便考虑)。可使用反斜杠(\)对引号进行转义
厌烦了反斜杠?你在本章后面将看到,在大多数情况下,可通过使用长字符串和原始字
符串(可结合使用这两种字符串)来避免使用反斜杠。
1.10.2 拼接字符串
为处理前述不太正常的示例,来看另一种表示这个字符串的方式:
>>> "Let's say " '"Hello, world!"'
'Let\'s say "Hello, world!"'
我依次输入了两个字符串,而Python自动将它们拼接起来了(合并为一个字符串)。这种机
制用得不多,但有时候很有用。然而,仅当你同时依次输入两个字符串时,这种机制才管用。
>>> x = "Hello, "
>>> y = "world!"
>>> x y
SyntaxError: invalid syntax
换而言之,这是一种输入字符串的特殊方式,而非通用的字符串拼接方法。那么应该如何拼
接字符串呢?就像将数相加一样,将它们相加:
>>> "Hello, " + "world!"
'Hello, world!'
>>> x = "Hello, "
>>> y = "world!"
>>> x + y
'Hello, world!'
1.10.3 字符串表示 str 和 repr
Python打印所有的字符串时,都用引号将其括起。你可能通过前面的示例发现了这一点。这
是因为Python打印值时,保留其在代码中的样子,而不是你希望用户看到的样子。但如果你使用
print,结果将不同。
>>> "Hello, world!"
'Hello, world!'
>>> print("Hello, world!")
Hello, world!
如果再加上表示换行符的编码\n,差别将更明显。
>>> "Hello,\nworld!"
'Hello,\nworld!'
>>> print("Hello,\nworld!")
Hello,
world!
通过两种不同的机制将值转换成了字符串。你可通过使用函数str和repr①直接使用这两种机
制。使用str能以合理的方式将值转换为用户能够看懂的字符串。例如,尽可能将特殊字符编码
转换为相应的字符。然而,使用repr时,通常会获得值的合法Python表达式表示。
repr 是representation及描述的意思,不是对人的描述,而是对python机器的描述
>>> print(repr("Hello,\nworld!"))
'Hello,\nworld!'
>>> print(str("Hello,\nworld!"))
Hello,
world!
① 实际上,像int一样,str也是一个类,但repr是一个函数
1.10.4 长字符串、原始字符串和字节
有一些独特而有用的字符串表示方式。例如,有一种独特的语法可用于表示包含换行符或反
斜杠的字符串(长字符串和原始字符串)。对于包含特殊符号的字符串,Python 2还提供了一种专
用的表示语法,结果为Unicode字符串。这种语法现在依然管用,但是多余,因为在Python 3中,
所有的字符串都是Unicode字符串。Python 3还引入了一种新语法,用于表示大致相当于老式字符
串的字节对象。你将看到,在处理Unicode编码方面,这种对象依然扮演着重要的角色。
- 长字符串
要表示很长的字符串(跨越多行的字符串),可使用三引号(而不是普通引号)。
print('''This is a very long string. It continues here.
And it's not over yet. "Hello, world!"
Still here.''')
还可使用三个双引号,如"""like this"""。请注意,这让解释器能够识别表示字符串开始
和结束位置的引号,因此字符串本身可包含单引号和双引号,无需使用反斜杠进行转义。
- 原始字符串
原始字符串不以特殊方式处理反斜杠,因此在有些情况下很有用②。在常规字符串中,反斜
杠扮演着特殊角色:它对字符进行转义,让你能够在字符串中包含原本无法包含的字符。例如,
你已经看到可使用\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\baz\frozz\bozz'
在这样的情况下,原始字符串可派上用场,因为它们根本不会对反斜杠做特殊处理,而是让
字符串包含的每个字符都保持原样。
>>> print(r'C:\nowhere')
C:\nowhere
>>> print(r'C:\Program Files\fnord\foo\bar\baz\frozz\bozz')
C:\Program Files\fnord\foo\bar\baz\frozz\bozz
如你所见,原始字符串用前缀r表示。看起来可在原始字符串中包含任何字符,这大致是正
确的。一个例外是,引号需要像通常那样进行转义,但这意味着用于执行转义的反斜杠也将包含
在最终的字符串中。
raw:adj. 生的;未加工的;阴冷的;刺痛的;擦掉皮的;无经验的;(在艺术等方面)不成熟的
原始字符串是为正则表达式设计的,也可以用来方便地表示Windows系统下的路径,不过,如果路径以"" 结尾那么会出错。
当反斜杠后跟原始字符串中的引号时,将对其进行转义。 但是,反斜杠也保留在结果中。 由于此功能,我们无法创建单反斜杠的原始字符串。 另外,原始字符串的末尾不能有奇数个反斜杠。
>>> print(r'Let\'s go!')
Let\'s go!
另外,原始字符串不能以单个反斜杠结尾。换而言之,原始字符串的最后一个字符不能是反
斜杠,除非你对其进行转义(但进行转义时,用于转义的反斜杠也将是字符串的一部分)。根据
前一个示例,这一点应该是显而易见的。如果最后一个字符(位于结束引号前面的那个字符)为
反斜杠,且未对其进行转义,Python将无法判断字符串是否到此结束。
>>> print(r"This is illegal\")
SyntaxError: EOL while scanning string literal
这合乎情理,但如果要指定以反斜杠结尾的原始字符串(如以反斜杠结尾的DOS路径),该
如何办呢?本节介绍了大量技巧,应该能够帮助你解决这个问题,但基本技巧是将反斜杠单独作
为一个字符串,下面是一个简单的示例:
>>> print(r'C:\Program Files\foo\bar' '\\')
C:\Program Files\foo\bar\
请注意,指定原始字符串时,可使用单引号或双引号将其括起,还可使用三引号将其括起。
② 编写正则表达式时,原始字符串很有用,这将在第10章详细介绍。
- Unicode、bytes和bytearray
有一种指定Unicode字符的通用
机制:使用16或32位的十六进制字面量(分别加上前缀\u或\U)或者使用字符的Unicode名称
(\N{name})。
为与C语言互操作
以及将文本写入文件或通过网络套接字发送出去,Python提供了两种类似的类型:不可变的bytes
和可变的bytearray。如果需要,可直接创建bytes对象(而不是字符串),方法是使用前缀b:
>>> b'Hello, world!'
b'Hello, world!'
然而,1字节只能表示256个不同的值,离Unicode标准的要求差很远。Python bytes字面量只
支持ASCII标准中的128个字符,而余下的128个值必须用转义序列表示,如\xf0表示十六进制值
0xf0(即240)。
然而,有一种非常巧妙的替代方式:不使用全部32位,而是使用变长编码,即对于不同的字
符,使用不同数量的字节进行编码。这种编码方式主要出自计算机先锋Kenneth Thompson之手。
通过使用这种编码,可节省占用的空间,就像摩尔斯码使用较少的点和短线表示常见的字母,从
而减少工作量一样①。具体地说,进行单字节编码时,依然使用ASCII编码,以便与较旧的系统兼
容;但对于不在这个范围内的字符,使用多个字节(最多为6个)进行编码。下面来使用ASCII、
UTF-8和UTF-32编码将字符串转换为bytes。
>>> "Hello, world!".encode("ASCII")
b'Hello, world!'
>>> "Hello, world!".encode("UTF-8")
b'Hello, world!'
>>> "Hello, world!".encode("UTF-32")
b'\xff\xfe\x00\x00H\x00\x00\x00e\x00\x00\x00l\x00\x00\x00l\x00\x00\x00o\x00\x00\x00,\x00\
x00\x00 \x00\x00\x00w\x00\x00\x00o\x00\x00\x00r\x00\x00\x00l\x00\x00\x00d\x00\x00\x00!\x00\
x00\x00'
可不使用方法encode和decode,而直接创建bytes和str(即字符串)对象,如下所示:
>>> bytes("Hællå, wørld!", encoding="utf-8")
b'H\xc3\xa6ll\xc3\xa5, w\xc3\xb8rld!'
>>> str(b'H\xc3\xa6ll\xc3\xa5, w\xc3\xb8rld!', encoding="utf-8")
'Hællå, wørld!'
源代码也将被编码,且默认使用的也是UTF-8编码。如果你想使用其他编码(例如,如果
你使用的文本编辑器使用其他编码来存储源代码),可使用特殊的注释来指定。
-- coding: encoding name --
请将其中的encoding name替换为你要使用的编码(大小写都行),如utf-8或latin-1。
最后,Python还提供了bytearray,它是bytes的可变版。从某种意义上说,它就像是可修改
的字符串——常规字符串是不能修改的。然而,bytearray其实是为在幕后使用而设计的,因此
作为类字符串使用时对用户并不友好。例如,要替换其中的字符,必须将其指定为0~255的值。
因此,要插入字符,必须使用ord获取其序数值(ordinal value)。
>>> x = bytearray(b"Hello!")
>>> x[1] = ord(b"u")
>>> x
bytearray(b'Hullo!')