Python简短教程
本文快速介绍Python这门语言,目标是在阐明Python的大部分特性的同时,又不会太过纠缠于特殊的规则或细节。为此,简要讲述一些基本概念,如变量、表达式、控制流、函数、生成器、类和输入/输出。本文不追求大而全,但有经验的程序员应该能够从本文中的资料推而广之,创建出更加高级的程序。初学者应该多尝试一些实例,才能对这门语言有一定的了解。
1.1 运行Python
Python程序是由解释器来执行的。通常只要在命令shell中输入python
即可启动解释器。当然,解释器以及开发环境有多种选择。解释器启动之后将出现一个命令提示,在此开始输入程序。
Python的交互模式是它最有用的功能之一。在交互式shell中,可以输入任意合法的语句或语句序列,然后查看结果。甚至很多人把它当作计算器使用,例如:
特殊变量
_
用于保存最后一次运算的结果,当然,这只在交互式工作时才有定义。
如果你要执行重复运行的程序,可以将语句放到一个文件中:
# helloworld.py
print("Hello World")
Python源文件是普通的文本文件,后缀通常是.py。#
字符表示整行都是注释。
要执行helloworld.py文件,可以通过如下命令:
python helloworld.py
在Windows中,双击一个.py文件或者在Windows开始菜单的“运行”命令中输入程序名称,均可启动Python程序。
1.2 变量和算术表达式
下面通过一个简单的例子,说明变量和表达式的用法。
principal = 1000 # 初始金额
rate = 0.05 # 利率
numyears = 5 # 年数
year = 1
while year <= numyears:
principal = principal * (1 + rate)
print year, principal
year += 1
运行结果如下:
Python是一种动态类型的语言,在程序执行过程中,可将变量名称绑定到不同的值,而且这些值可以属于不同的类型。赋值运算符的作用仅仅是在名称和值之间创建一种关联。尽管每个值都有一个相关类型,如integer或string,但变量名称是无类型的,在执行过程中可以引用任意类型的数据。这与C语言不同,例如在C语言中,名称代表了用于保存值的固定类型、大小和内存位置。Python的动态行为可以从程序中的principal变量看出来。最初给它分配的是一个integer值,但程序稍后给它重新赋了值,如下所示:
principal = principal * (1 + rate)
这条语句对表达式求值,并把名称principal重新与结果关联。principal的原始值是整数类型的1000,但现在的新值是浮点数(rate被定义为浮点数,因此上述表达式的值也是浮点数)。因此在程序中,principal的显式类型从integer动态变为了float。然而准确地说,不是principal的类型变了,而是principal名称引用的值的类型变了。
换行代表一条语句的结束。然而,也可以在同一行上使用分号来隔开多条语句,如下所示:
principal = 1000; rate = 0.05; numyears = 5;
while语句对随后的条件表达式进行测试。如果被测试的语句为True,while语句的主体就会执行。然后再次测试条件,再执行主体,直到条件变为False。因为循环主体是由缩进表示的,每次循环时都会执行程序中while之后的3条语句。Python不会指定所需缩进的量,只要在一个代码块中保持一致即可。然而,每个缩进层次使用4个空格是最常见的情况,而且通常也建议这么做。
程序中的程序有一个问题,即输出不是很美观。为了改进这一点,可以让各列右对齐,并将principal的精度限制为两位。实现这种格式有几种方法。最常用的方法是使用字符串格式化运算符%
,如下所示:
格式化字符串包含普通文本和特殊的格式化字符序列,如"%d"、"%s"和"%f"。这些序列分别用于指定特定类型数据的格式,如整数、字符串或浮点数。特殊的字符序列还可以包含用于指定宽度和精度的修饰符。例如,"%3d"将一个整数格式化为在一个宽度为3的列中右对齐,而"%0.2f"将一个浮点数格式化为在小数点后只出现两位数字。格式化字符串的行为与C语言中的printf()函数几乎完全相同。
更新的字符串格式化的方法是使用format()函数单独格式化每个部分。例如:
print(format(year,"3d"),format(principal,"0.2f"))
1.3 条件语句
if与else语句可执行简单的测试,如下所示:
if a < b:
print("Computer says Yes")
else:
print("Computer says No")
if和else子句的主体是用缩进表示的。else子句是可选的。
要创建一条空子句,可以使用pass语句,如下所示:
if a < b:
pass # Do nothing
else:
print("Computer says No")
使用or、and和not关键字可以建立布尔类型的表达式:
if product == "game" and type == "pirate memory" \
and not (age < 4 or age > 8):
print("I'll take it!")
注意编写复杂的测试用例通常需要编写很长的代码行,看起来令人生厌。为了提高代码的可读性,可以像上面一样在一行的结尾使用反斜杠(\),然后就可以在下一行继续书写上一条语句的内容。如果这样做,正常的缩进规则不适用于下一行,因此可以随意设置后续行的格式。
Python没有专门的switch或case语句用于测试多个值。要处理多个测试用例,可以使用elif语句,如下所示:
if suffix == ".htm":
content = "text/html"
elif suffix == ".jpg":
content = "image/jpeg"
elif suffix == ".png":
content = "image/png"
else:
raise RuntimeError("Unknown content type")
要表示真值,可以使用布尔值True和False,例如:
if 'spam' in s:
has_spam = True
else:
has_spam = False
所有关系运算符(如<和>)的结果都返回Ture或False。本例中使用的in运算符通常用于检查某个值是否包含在另一个对象(字符串、列表或字典)中。它也返回Ture或False,因此前一个例子可以缩短为:
has_spam = 'spam' in s
1.4 文件输入和输出
以下程序可打开一个文件并逐行读取该文件的内容:
f = open("foo.txt") #返回一个文件对象
line = f.readline() #调用文件的readline()方法
while line:
print line, #后面跟','将忽略换行符
# print(line,end='') #在Python 3中使用
line = f.readline()
f.close()
open()
函数返回一个新的文件对象。调用该对象上的方法可以执行各种文件操作。readline()
方法读取一行内容,包括结尾的换行符在内。读至文件结尾时将返回空字符串。
在这个例子中,程序只是循环读取了文件foo.txt中的所有行。如果程序在像这样的数据集(例如输入中的行、数字、字符串等)上进行循环,那么通常就称为迭代。因为迭代是很常见的一种操作,所以Python为其提供了一条专用语句for,用于迭代内容项。例如,同样的程序可以写成下面这种更简洁的形式:
for line in open("foo.txt"):
print(line)
要将程序的输出送到一个文件中,可将print语句改为以下内容:
print("%3d %0.2f" % (year,principal),ffile=f)
另外,文件对象支持使用write()
方法写入原始数据。例如,前一例子中的print语句也可以写成下面这样:
f.write("%3d %0.2f\n" % (year,principal))
尽管这些例子处理的都是文件,但同样的技术也适用于标准的解释器输出流和输入流。例如,要想用交互式方法读取用户输入,可以从文件sys.stdin
中读取。如果要将数据输出到屏幕上,可以写入文件sys.stdout
中,这与在输出print语句所生成数据时所用的文件是同一个文件。例如:
import sys
sys.stdout.write("Enter your name :")
name = sys.stdin.readline()
这段代码还可以简化为:input(),但它们的工作方式完全相同。
1.5 字符串
要创建一个字符串字面量,将字符串放在单引号、双引号或三引号中即可,如下所示:
a = "Hello World"
b = 'Python is groovy'
c = """Computer says 'No'"""
字符串前后使用的引号必须是对应匹配的。两个三引号之间出现的所有文本都视为字符串的内容,而使用单引号和双引号指定的字符串必须在一个逻辑行上。当字符串字面量的内容需放在多个文本行上时,三引号字符串就很有用,如下所示:
print('''Content-type: text/html
<h1> Hello World </h1>
Click <a href="http://www.python.org">here</a>.
''' )
字符串存储在一个以0开始、使用整数索引的字符序列中。要提取其中的一个字符,可以使用索引运算符s[i],如下所示:
a = "Hello World"
b = a[4] # b = 'o'
要提取一个子字符串,可以使用切片运算符s[i:j]。这会提取字符串s中索引位置k处的所有字符,其中索引k的范围是i<=k<j。如果省略i,则假定使用字符串的起始位置,如果省略j,则假定使用字符串的结尾位置:
c = a[:5] # c = "Hello"
d = a[6:] # d = "World"
e = a[3:8] # e = "lo Wo"
可以使用加(+)运算符连接两个字符串:
g = a + " This is a test"
Python绝不会把字符串的值解释为数值数据(像Perl或PHP等语言中会这样解释)。例如,+运算符始终会连接字符串:
x = "37"
y = "42"
z = x + y # z = "3742" (字符串连接)
要执行数学计算,首先要使用像int()或float()函数将字符串转换为数值,例如:
z = int(x) + int(y)
使用str()、repr()或format()函数可将非字符串值转换为字符串表示形式,例如:
s = "The value of x is " + str(x)
s = "The value of x is " + repr(x)
s = "The value of x is " + format(x,"4d")
尽管str()和repr()都可以创建字符串,但它们的输出通常存在细微的差别。str()生成的输出与使用print语句得到的输出相同,而用repr()创建的字符串可表示程序中某个对象的精确值,例如:
>>> x = 3.4
>>> str(x)
'3.4'
>>> repr(x)
'3.3999999999999999'
>>>
上例中3.4的不精确表示并非是Python中的一个bug。这是双精度浮点数的一个特点,因为从设计上说,底层计算机硬件无法精确地表示十进制小数。
format()函数的作用是利用特定格式将值转换成字符串,例如:
>>> format(x,"0.5f")
'3.40000'
>>>
1.6 列表
列表是任意对象的序列。把值放入方括号中就可以创建列表,如下所示:
names = [ "Dave", "Mark", "Ann", "Phil" ]
列表使用从0开始的整数索引,使用索引运算符可以访问并修改列表中的项:
a = names[2] #返回列表的第3项"Ann"
names[0] = "Jeff" #将第1项改为"Jeff"
要将新项追加到列表末尾,可使用append()方法:
names.append("Paula")
要将一项插入到列表中,可使用insert()方法:
names.insert(2, "Thomas")
使用切片运算符可以提取一个子列表或对子列表重新赋值:
b = names[0:2] #返回[ "Jeff", "Mark" ]
c = names[2:] #返回[ "Thomas", "Ann", "Phil", "Paula" ]
names[1] = 'Jeff' #将names中的第2项替换为'Jeff'
names[0:2] = ['Dave','Mark','Jeff'] #将列表的头两项替换为右边的列表
使用加(+)运算符可以连接列表:
a = [1,2,3] + [4,5] # 结果是[1,2,3,4,5]
创建一个空列表有两种方式:
names = []# 一个空列表
names = list()# 一个空列表
列表可以包含任意种类的Python对象,包括其他列表在内,如下例所示:
a = [1,"Dave",3.14, ["Mark", 7, 9, [100,101]], 10]
嵌套列表中包含的项需要使用多次索引运算才能访问到,例如:
a[1] # 返回 "Dave"
a[3][2] # 返回 9
a[3][3][1] # 返回 101
以下程序展示了列表的一些高级特性,该程序会读取在命令行上指定的一个文件中的数值列表,然后输出其中的最大值和最小值。
#列表的高级特性
import sys# 加载sys模块
if len(sys.argv) != 2# 检查命令行参数的数量:
print("Please supply a filename")
raise SystemExit(1)
f = open(sys.argv[1]) # 命令行上的文件名
lines = f.readlines() # 将所有行读取到一个列表中
f.close()
# 将所有输入值从字符串转换为浮点数
fvalues = [float(line) for line in lines]
# 打印最小值和最大值
print "The minimum value is ", min(fvalues)
print "The maximum value is ", max(fvalues)
该程序的第一行使用import
语句从Python库加载sys模块。加载该模块的目的是获得命令行参数。
open()
函数使用了一个文件名,该文件名是以命令行选项的形式提供的并保存在列表sys.argv
中。readline()
方法将所有输入行读取到一个字符串列表中。
表达式[float(line) for line in line]
通过对列表lines中的所有字符串进行循环,并对每个元素应用函数float(),从而构造一个新列表。这种功能特别强大的列表构造方法叫做列表包含(list comprehension)。因为你还可以使用for循环来读取文件中的行,所以可以将上面程序中转换值的代码简化为一条语句:
fvalues = [float(line) for line in open(sys.argv[1])]
将输入行转换成一个浮点数列表后,再使用内置函数min()
和max()
计算出最大值和最小值即可。
1.7 元组
要创建简单的数据结构,可以使用元组将一组值打包到一个对象中。在圆括号中放入一组值即可创建元组,例如:
stock = ('GOOG', 100, 490.10)
address = ('www.python.org', 80)
person = (first_name, last_name, phone)
即使没有圆括号,Python通常也能识别出元组:
stock = 'GOOG', 100, 490.10
address = 'www.python.org',80
person = first_name, last_name, phone
为了完整起见,也可以定义0个和1个元素的元组,但语法较为特殊:
a = () # 0-元组 (空元组)
b = (item,) # 1-元组 (注意随后的逗号)
c = item, # 1-元组 (注意随后的逗号)
和列表一样,也可以使用数字索引来提取元组中的值。然而,更常见的做法是将元组解包为一组变量,例如:
name, shares, price = stock
host, port = address
first_name, last_name, phone = person
尽管元组支持的大部分操作与列表的相同(如索引、切片和连接),但创建元组后不能修改它的内容(即无法替换、删除现有元组中的元素或插入新元素)。这说明最好把元组看成一个由多个部分组成的对象,而不是可在其中插入或删除项的不同对象的集合。
因为元组与列表之间存在诸多相似之处,所以有些程序员往往完全忽略了元组,而只使用列表,因为后者看似更灵活。尽管这并无不可,但如果程序要创建大量的小列表(即包含的项少于12个),就会造成内存浪费。这是因为系统会为列表分配稍微多一些内存,以优化添加新项时的操作性能。而由于元组是不可变的,所以它们的表示更为紧凑,不会占据额外的内存空间。
表示数据时通常同时使用元组和列表。例如,下面的程序显示了如何读取包含不同数据列,且各数据列由逗号隔开的文件:
# 文件中各行的格式为"name,shares,price"
filename = "portfolio.csv"
portfolio = []
for line in open(filename):
fields = line.split(",")# 将每行划分为一个列表
name = fields[0]# 提取并转换每个字段
shares = int(fields[1])
price = float(fields[2])
stock = (name,shares,price)# 创建一个元组(name, shares, price)
portfolio.append(stock)# 将记录追加到列表中
字符串的split()
方法会按照指定的分隔符将一个字符串划分为一个字段列表。该程序最后创建的portfolio数据结构好比一个二维的行列数组,每行由一个元组表示,可通过如下方式访问:
>>> portfolio[0]
('GOOG', 100, 490.10)
>>> portfolio[1]
('MSFT', 50, 54.23)
>>>
每个数据项可以通过如下方式访问:
>>> portfolio[1][1]
50
>>> portfolio[1][2]
54.23
>>>
下面给出了一种循环所有记录并将字段展开到一组变量中的简单方法:
total = 0.0
for name, shares, price in portfolio:
total += shares * price
1.8 集合
集合用于包含一组无序的对象。要创建集合,可使用set()函数并像下面这样提供一系列的项:
s = set([3,5,9,10]) # 创建一个数值集合
t = set("Hello") # 创建一个唯一字符的集合
与列表和元组不同,集合是无序的,也无法通过数字进行索引。此外,集合中的元素不能重复。例如,如果检查前面代码中t集合的值,结果会是:
>>> t
set(['H', 'e', 'l', 'o'])
注意只出现了一个'l'。
集合支持一系列标准操作,包括并集、交集、差集和对称差集,例如:
a = t | s # t 和 s的并集
b = t & s # t 和 s的交集
c = t - s # 求差集(项在t中,但不在s中)
d = t ^ s # 对称差集(项在t或s中,但不会同时出现在二者中)
使用add()和update()可以在集合中添加新项:
t.add('x') # 添加一项
s.update([10,37,42]) # 在s中添加多项
使用remove()可以删除一项:
t.remove('H')
1.9 字典
字典就是一个关联数组或散列表,其中包含通过关键字索引的对象。在大括号({ })中放入值即可创建字典,如下所示:
stock = {
"name" : "GOOG",
"shares" : 100,
"price" : 490.10
}
要访问字典成员,可使用关键字索引运算符,如下所示:
name = stock["name"]
value = stock["shares"] * shares["price"]
插入或修改对象的方法是:
stock["shares"] = 75
stock["date"] = "June 7, 2007"
尽管字符串是最常用的关键字类型,但还可以使用其他的Python对象,包括数值和元组。但包括列表和字典在内的有些对象不能用作关键字,因为它们的内容可以发生变化。
如前所述,在定义一个可包含多个命名字段的对象时,字典是一种很有用的方式。然而,字典也可用作快速查找无序数据的一个容器。例如,下面是一个股票价格的字典:
prices = {
"GOOG" : 490.10,
"AAPL" : 123.50,
"IBM" : 91.50,
"MSFT" : 52.13
}
创建一个空字典有两种方式:
prices = {} # 一个空字典
prices = dict() # 一个空字典
使用in运算符可以测试某个内容项是不是字典成员,如下所示:
if "SCOX" in prices:
p = prices["SCOX"]
else:
p = 0.0
这个特殊的步骤序列还可以写成更简洁的形式,如下所示:
p = prices.get("SCOX",0.0)
要获得一个字典关键字的列表,将字典转换为列表即可:
syms = list(prices) # syms = ["AAPL", "MSFT", "IBM", "GOOG"]
使用del函数可以删除字典的元素:
del(prices["MSFT"])
字典是Python解释器中最完善的数据类型。因此,如果只是要在程序中存储和处理数据,使用字典比使用一些自定义数据结构要好得多。
1.10 迭代与循环
最常用的循环结构是用于迭代多个项的for语句。迭代是Python最重要的功能之一。但最常见迭代形式只是循环一个序列(如字符串、列表或元组)的所有成员,例如:
for n in [1,2,3,4,5,6,7,8,9]:
print("2 to the %d power is %d" % (n, 2**n))
在这个例子中,每次迭代都会将列表[1,2,3,4,...,9]中的下一个值赋给变量n。因为在整数范围内执行循环十分常见,为此经常会使用下面的快捷方法:
for n in range(1,10):
print("2 to the %d power is %d" % (n, 2**n))
range(i, j, [,步进值])
函数创建的对象表示值i到j-1的整数。如果起始值被省略,则认为是0。第三个参数是可选的步进值。例如:
a = range(5) # a = 0,1,2,3,4
b = range(1,8) # b = 1,2,3,4,5,6,7
c = range(0,14,3) # c = 0,3,6,9,12
d = range(8,1,-1) # d = 8,7,6,5,4,3,2
在使用range()函数时请注意,在Python 2中,它创建的值是已经用整数值完全填满的列表。当范围非常大时,这可能会在不经意间耗掉所有可用内存。因此,在老式的Python代码中,可能会看到程序员使用另一个函数xrange()。例如:
for i in xrange(100000000): # i = 0,1,2,...,99999999
statements
进行查找时,xrange()函数创建的对象会查询时根据需要计算它所表示的值。为此,它成为了表示极大范围整数值的首选方式。在Python 3中,xrange()函数已经更名为range(),并且已删除了老式range()函数的功能。
for语句并不仅限于处理整数序列,还可用于迭代多种对象,包括字符串、列表、字典和文件,例如:
a = "Hello World"
# 打印出a中的每个字符
for c in a:
print(c)
b = ["Dave","Mark","Ann","Phil"]
# 打印出一个列表的成员
for name in b:
print(name)
c = { 'GOOG' : 490.10, 'IBM' : 91.50, 'AAPL' : 123.15 }
# 打印出一个字典的所有成员
for key in c:
print(key, c[key])
# 打印一个文件中的所有行
f = open("foo.txt")
for line in f:
print(line)
for循环是Python最强大的语言功能之一,因为你可以创建自定义的迭代对象和生成器函数并给它提供值序列。本章稍后和第6章将会讲述有关迭代器和生成器的更多内容。
1.11 函数
使用def语句可以创建函数,如下例所示:
def remainder(a,b):
q = a // b#//是截断除法运算符
r = a - q*b
return r
要调用函数,只要使用函数名加上用圆括号括起来的参数即可,如result = remainder(37, 15)。如果要让函数返回多个值,可以使用元组,如下所示:
def divide(a,b):
q = a // b# 如果a和b是整数,q就是整数
r = a - q*b
return (q,r)
使用元组返回多个值时,可以很容易地将结果放到单独的变量中,例如:
quotient, remainder = divide(1456,33)
要给函数参数提供一个默认值,可使用以下赋值方式:
def connect(hostname,port,timeout=300):
# 函数体
在函数定义中给一个参数提供默认值以后,调用此函数时就可以省略该参数,此时该参数将使用默认值,如下所示:
connect('www.python.org', 80)
还可以使用关键字参数调用函数,此时可以按任意顺序提供参数,但这需要你知道函数定义中的参数名称,如下所示:
connect(port=80,hostname="www.python.org")
在函数中创建变量或给变量赋值时,该变量的作用域是局部的。也就是说,该变量只定义在函数体内部,而且当函数返回值后会立即销毁该变量。要在函数内部修改某个全局变量的值,可以使用global语句,如下所示:
count = 0
...
def foo():
global count
count += 1# 更改全局变量count
1.12 生成器
如果使用yield语句,可以让函数生成一个结果序列,而不仅仅是一个值,例如:
def countdown(n):
print("Counting down!")
while n > 0:
yield n # 生成一个值(n)
n -= 1
任何使用yield的函数都称为生成器。调用生成器函数将创建一个对象,该对象通过连续调用next()方法生成结果序列,例如:
>>> c = countdown(5)
>>> c.__next__()
Counting down!
5
>>> c.__next__()
4
>>> c.__next__()
3
>>>
next()调用使生成器函数一直运行到下一条yield语句为止。此时next()将返回值传递给yield,而且函数将暂时中止执行。再次调用next()时,函数将继续执行yield之后的语句。此过程持续到函数返回为止。
通常不会像上面这样手动调用next(),而是会使用一个for循环,例如:
>>> for i in countdown(5):
... print(i)
Counting down!
5
4
3
2
1
>>>
生成器是基于处理管道、流或数据流编写程序的一种极其强大的方式。例如,下面的生成器函数模拟了常用于监控日志文件的UNIX tail -f命令的行为:
# tail一个文件(如tail -f)
import time
def tail(f):
f.seek(0,2) # 移动到EOF
while True:
line = f.readline() # 尝试读取一个新的文本行
if not line:# 如果没有内容,暂时休眠并再次尝试
time.sleep(0.1)
continue
yield line
下面的生成器用于在很多行中查找特定的子字符串:
def grep(lines, searchtext):
for line in lines:
if searchtext in line: yield line
下面的例子将以上两个生成器合并在一起,创建了一个简单的处理管道:
# UNIX "tail -f | grep python"命令的python实现
wwwlog = tail(open("access-log"))
pylines = grep(wwwlog,"python")
for line in pylines:
print(line)
生成器的微妙之处在于,它经常和其他可迭代的对象(如列表或文件)混合在一起,特别是在编写如for item in s
这样的语句时,s可以表示项目的列表、文件的各行、生成器函数的结果,或者可支持迭代的其他任何对象。能够在s 中插入不同对象,这在创建可扩展的程序时可以发挥巨大的作用。
1.13 协程
通常,函数运行时要使用一组输入参数。但是,也可以把函数编写为一个任务,从而能处理发送给它的一系列输入。这类函数称为协程,可使用yield语句并以表达式(yield)的形式创建协程,如下所示:
def print_matches(matchtext):
print "Looking for",matchtext
while True:
line = (yield) # 获得一行文本
if matchtext in line:
print(line)
要使用这个函数,首先要调用它,向前执行到第一条(yield)语句,然后使用send()给它发送数据,例如:
>>> matcher = print_matches("python")
>>> matcher.next() # 向前执行到第一条(yield)语句
Looking for python
>>> matcher.send("Hello World")
>>> matcher.send("python is cool")
python is cool
>>> matcher.send("yow!")
>>> matcher.close() # 匹配器函数调用结束
>>>
使用send()为协程发送某个值之前,协程会暂时中止。此时,协程中的(yield)表达式将返回这个值,而接下来的语句将会处理它。处理直到遇到下一个(yield)表达式才会结束,也就是函数暂时中止的地方。正如上一个例子所示,这个过程将会继续下去,直到协程函数返回或者调用它的close()方法为止。
基于生产者-使用者模型(即一部分程序生成的数据会被程序的另一部分使用)编写并发程序时,协程十分有用。在这种模型中,协程代表了数据的一个使用者。下面给出了共同使用生成器和协程的一个例子:
# 一组匹配器协程
matchers = [
print_matches("python"),
print_matches("guido"),
print_matches("jython")
]
# 通过调用next()准备所有的匹配器
for m in matchers: m.next()
# 为所有匹配器提供一个活动的日志文件,为此
# 必须有一台活动的Web服务器将数据写入日志
wwwlog = tail(open("access-log"))
for line in wwwlog:
for m in matchers:
m.send(line) # 将数据发送到每个匹配器协程中
1.14 对象和类
程序中使用的所有值都是对象。对象由内部数据和各种方法组成,这些方法会执行与这些数据相关的各种操作。前面在处理像字符串和列表这样的内置类型时,就已经用到了对象和方法。例如:
items = [37, 42]# 创建一个列表对象
items.append(73)# 调用append()方法
dir()
函数可以列出对象上的可用方法,是进行交互式试验的有用工具,例如:
>>> items = [37, 42]
>>> dir(items)
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__',
...
'append', 'count', 'extend', 'index', 'insert', 'pop',
'remove', 'reverse', 'sort']
>>>
查看对象的可用方法时,会看到诸如append()和insert()等很熟悉的方法。但也可以看到始终以双下划线开始和结束的特殊方法。这些方法用于实现各种语言的运算方式。例如,add()方法实现了+运算符的功能:
>>> items.__add__([73,101])
[37, 42, 73, 101]
>>>
在面向对象的编程中,class语句用于定义新的对象类型。例如,下面的类定义了带有push()、pop()和length()操作的简单栈:
class Stack(object):
def __init__(self):# 初始化栈
self.stack = [ ]
def push(self,object):
self.stack.append(object)
def pop(self):
return self.stack.pop()
def length(self):
return len(self.stack)
在类定义的第一行中,语句class Stack(object)
将Stack声明为一个object。使用圆括号是Python指定继承的方式--在这个例子中,Stack继承自object,object也是所有Python类型的根类型。类定义中使用def语句定义了方法。每个方法中的第一个参数始终指向对象本身。根据约定,该参数使用名称self。涉及对象属性的所有操作都必须显式引用self变量。以双下划线开始和结束的方法是特殊的方法。例如,init用于在创建对象后初始化该对象。
要想使用类,可编写如下所示的代码:
s = Stack()# 创建一个栈
s.push("Dave")# 在栈中放入一些内容
s.push(42)
s.push([3,4,5])
x = s.pop()# x的值为[3,4,5]
y = s.pop()# y的值为42
del(s) # 删除s
这个例子创建了一个全新的对象来实现栈。但是,栈与内置的列表对象几乎完全相同。因此,继承list然后添加一个额外的方法也是可行的:
class Stack(list):
# 为栈接口添加push()方法
# 注意:列表已经提供了一个pop()方法
def push(self,object):
self.append(object)
通常,类中定义的所有方法只适用于该类的实例(即创建的对象)。但是,也可以定义不同种类的方法,如C++和Java程序员所熟知的静态方法,例如:
class EventHanlder(object):
@staticmethod
def dispatcherThread():
while (1):
# 等待请求
EventHandler.dispatcherTread() # 像函数一样调用方法
在这个例子中,@staticmethod将方法声明为静态方法。@staticmethod是使用装饰器
1.15 异常
如果程序中出现错误,就会引发异常,并显示类似下面的追踪消息:
Traceback (most recent call last):
File "foo.py", line 12, in <module>
IOError: [Errno 2] No such file or directory: 'file.txt'
该追踪消息指出了所发生的错误类型及位置。通常情况下,错误会导致程序终止。但是可以使用try和except语句捕捉并处理异常,如下所示:
try:
f = open("file.txt","r")
except IOError as e:
print(e )
如果出现IOError,引发错误的详细信息将被放在对象e中,然后控制权被传递给except代码块中的代码。如果出现其他类型的异常,对象e将被传递给用于处理这些异常的代码块(如果有的话)。如果没有出现错误,except代码块中的代码将被忽略。处理完异常后,程序将继续执行紧跟在最后一个except代码块后面的语句。程序不会返回到发生异常的位置。
raise语句用于手工引发异常。引发异常时,可以使用任意一个内置异常,如下所示:
raise RuntimeError("Computer says no")
你也可以创建自己的异常。
在使用异常处理时,如何正确地管理系统资源(如锁定、文件和网络连接)通常是一个棘手的问题。为了简化此类编程,可以对某类对象使用with语句。下面的例子给出了使用互斥锁的代码:
import threading
message_lock = threading.Lock()
...
with message_lock:
messages.add(newmessage)
在这个例子中,with
语句执行时会自动获取message_lock对象。当执行离开with
代码块环境后,锁定将被自动释放。无论with
代码块内部发生了什么,这种管理都会进行。例如,如果出现一个异常,当控制离开代码块环境时将释放锁定。
with
语句通常只适用于与系统资源或执行环境相关的对象,如文件、连接和锁定。但是,用户定义的对象也可以定义它们自己的自定义处理。
1.16 模块
随着程序变得越来越大,为了便于维护,需要把它分为多个文件。为此,Python允许把定义放入一个文件中,然后在其他程序和脚本中将其作为模块导入。要创建模块,可将相关的语句和定义放入与模块同名的文件中(注意,该文件的后缀必须是.py)。例如:
# file :div.py
def divide(a,b):
q = a/b # 如果a和b是整数,则q也是整数
r = a - q*b
return (q,r)
要在其他程序中使用该模块,可以使用import语句:
import div
a, b = div.divide(2305, 29)
import语句创建了一个新的命名空间,并在该命名空间中执行与.py文件相关的所有语句。要在导入后访问命名空间的内容,只要使用该模块的名称作为前缀,正如上面例子中的div.divide()一样。
如果要使用不同的名称导入模块,可以给import语句加上可选的as限定符,如下所示:
import div as foo
a,b = foo.divide(2305,29)
要将具体的定义导入到当前的命名空间中,可使用from语句:
from div import divide
a,b = divide(2305,29) # 不再使用div前缀
要把模块的所有内容加载到当前的命名空间中,还可以使用以下语句:
from div import *
与对象一样,dir()函数可以列出模块的内容,是进行交互式试验的有用工具:
>>> import string
>>> dir(string)
['__builtins__', '__doc__', '__file__', '__name__', '_idmap',
'_idmapL', '_lower', '_swapcase', '_upper', 'atof', 'atof_error',
'atoi', 'atoi_error', 'atol', 'atol_error', 'capitalize',
'capwords', 'center', 'count', 'digits', 'expandtabs', 'find',
...
>>>
1.17 获得帮助
使用Python时,有几个快速获取可用信息的来源。首先,以交互模式运行Python时,可以使用help()命令获得有关内置模块和Python其他方面的信息。单独输入help()将获得一般信息,而输入help('模块名')则可获得具体模块的信息。如果提供函数名称,help()命令还可以返回该函数的详细信息。
大多数Python函数都有描述该函数用途的文档说明。要打印这些文档的内容,只要打印__doc__
属性即可。例如:
>>> print issubclass.__doc__
issubclass(C, B) -> bool
Return whether class C is a subclass (i.e., a derived class) of class B.
When using a tuple as the second argument issubclass(X, (A, B, ...)),
is a shortcut for issubclass(X, A) or issubclass(X, B) or ... (etc.).
>>>
最后但也很重要的一点是,大多数Python安装还包括了命令pydoc。该命令用于返回有关Python模块的文档。只需在系统命令提示符后输入pydoc主题即可。
以上。