1 基本程序要素
Python 程序示例:猜数字
我们来看一个完整的 Python 程序,它是一个和用户玩猜数字的游戏的程序。
计算机要求用户输入数值范围的最小值和最大值。计算机随后“思考”出在这个范围之内的一个随机数,并且重复地要求用户猜测这个数,直到用户猜对了。在用户每次进行猜测之后,计算机都会给出一个提示,并且会在这个过程的最后显示出总的猜测次数。这个程序包含了几种类型的 Python 语句,例如,输入语句、输出语句、赋值语句、循环和条件语句,我们将在本章的后面介绍它们。这个程序还包含了一个函数的定义。
如下是该程序的代码,代码位于文件 numberguess.py 之中:
"""
Author: Ken Lambert
Plays a game of guess the number with the user.
"""
import random
def main():
"""Inputs the bounds of the range of numbers,
and lets the user guess the computer's number until
the guess is correct."""
smaller = int(input("Enter the smaller number: "))
larger = int(input("Enter the larger number: "))
myNumber = random.randint(smaller, larger)
count = 0
while True:
count += 1
userNumber = int(input("Enter your guess: "))
if userNumber < myNumber:
print("Too small")
elif userNumber > myNumber:
print("Too large")
else:
print("You've got it in", count, "tries!")
break
if __name__ == "__main__":
main()
如下是一个用户和该程序的交互记录:
Enter the smaller number: 1
Enter the larger number: 32
Enter your guess: 16
Too small
Enter your guess: 24
Too large
Enter your guess: 20
You’ve got it in 3 tries!
你可以在一个终端窗口中输入一条命令,来运行整个 Python 程序。要运行 numberguess.py 文件中包含的程序,在大多数的终端窗口中,只要输入如下的命令就可以了:
python numberguess.py
程序注释
# This is an end-of-line comment.
语法元素
一种语言的语法元素是由词法元素组成的语句(表达式、语句和其他结构)的类型。和大多数的高级语言不同,Python 使用空白(空格、制表符和换行)来标记多种类型的语句的语法。这意味着,在 Python 代码中,缩进和换行是很重要的。我使用 4 个空格宽度的缩进。
字面值
数字(整数或浮点数)的写法和在其他编程语言中的写法都是一样的。布尔值 True 和False 都是关键字。一些数据结构,例如字符串、元组、列表和字典,也拥有字面值,稍后我们将会看到这一点
字符串字面值
可以用单引号、双引号,或者成对的三个双引号或三个单引号将字符串括起来。最后的这种表示方法,对于包含多行文本的字符串来说,是很有用的。字符值是单字符的字符串。\字符用于将非图形化的字符(例如,换行\n 和制表符\t,或者\字符本身)进行转义。下面的代码段及其输出,展示了各种可能性:
print("Using double quotes")
print(’Using single quotes’)
print("Mentioning the word ’Python’ by quoting it")
print("Embedding a\nline break with \\n")
print("""Embedding a
line break with triple quotes""")
输出
Using double quotes
Using single quotes
Mentioning the word ’Python’ by quoting it
Embedding a
line break with \n
Embedding a
line break with triple quotes
运算符和表达式
算术表达式使用标准的运算符(+、–、和/)和中缀表示法。/运算符对于任何数字运算数,都会生成一个浮点数的结果,而//运算符则会产生一个整数的商。当+运算符用于集合(例如,字符串和列表)的时候,它表示连接。*运算符用于指数运算。
比较运算符(<、<=、>、>=、==和!=)用于数字和字符串。==运算符比较数据结构内部的内容(例如,两个列表)在结构上的相等性。is 运算符比较对象实体的两个值。比较会返回 True 或 False。
逻辑运算符 and、or 和 not 会将几个值(例如 0、None、空字符串和空列表)作为False 对待。相反,它们会将大多数其他的 Python 值当作 True 对待。
下标运算符[]和集合一起使用,稍后将会介绍它。
选择器运算符.用于引用一个模块、类或对象中的一个具名的项。
函数调用
函数调用的通常形式是函数名后面跟着用括号括起来的参数列表。例如:
min(5, 2) # Returns 2
Python 包含了一些标准的函数,例如 abs 和 round。很多其他的函数,可以通过导入模块的方式而变得可用,稍后你将会看到这一点。
print 函数(输出)
标准输出函数 print 会将其参数显示到控制台。这个函数允许不同个数的参数。Python自动在每个参数上运行 str 函数,以获取其字符串表示,并且在输出之前用一个空格将每一个字符串隔开。默认情况下,输出结束的时候会使用一个换行。
input 函数(输入)
标准输入函数 input 会等待用户通过键盘输入文本。当用户按下回车键的时候,该函数返回了包含所输入的字符的一个字符串。这个函数接受一个可选的字符串作为其参数,并且会不换行地打印出这个字符串,以提示用户进行输入。
name = input("Enter the smaller number: ")
类型转换函数和混合模式运算
可以将一些数据类型名称当作类型转换函数来使用。例如,当用户输入一个数字的时候,input 函数返回数字组成的一个字符串,而不是一个数字值。程序必须先把这个字符串转换为 int 类型或 float 类型,然后才能够进行数字处理。下面的代码段输入了一个圆的半径,将这个字符串转换为 float 型,然后计算并输出了这个圆形的面积:
radius = float(input("Radius: "))
print("The area is", 3.14 * radius ** 2)
变量和赋值语句
一个 Python 变量是通过一条赋值语句来引入的。例如:
PI = 3.1416
将 PI 的值设为 3.1416。一条简单的赋值语句的语法是:
<identifier> = <expression>
在同一条赋值语句中可以引入多个变量,如下所示:
minValue, maxValue = 1, 100
要交换变量 a 和变量 b 的值,可以像下面这样编写代码:
a, b=b,a
Python 数据类型
在 Python 中,任何变量都可以指定任何类型的一个值。变量不会声明为某一个类型,这和很多其他语言中的情况不同,变量会被直接赋一个值。
因此,数据类型名称不会出现在 Python 程序中。然而,所有的值和对象都是有类型的。
表达式中的运算数的类型会在运行时检查,因此,类型错误一定会进行检查;然而,在编写代码的时候,程序员不必关心数据类型。
import 语句
import 语句使得另一个模块中的标识符可以被一个程序所见到。这些标识符可能是对象名、函数名或类名。有几种方式可以表示一条 import 语句。最简单的方式是,导入一个模块名称,例如:
import math
这使得在 math 模块中定义的任何名称,在当前的模块中都可以通过 math.<name>形式的语法而变得可用。因此,math.sqrt(2)将会返回 2 的平方根。
另一种导入的形式是,只导入要使用的名称自身,这样一来,就可以不带有模块名作为前缀而直接使用该名称了:
from math import sqrt
print(sqrt(2))
可以通过列出几个单独的名称来导入它们:
from math import pi, sqrt
print(sqrt(2) * pi)
可以使用*运算符导入一个模块中所有的名称,但是,我们并不认为这是好的做法。
2 控制语句
Python 包含了针对序列、条件式执行和迭代等情况的、常见的各种控制语句。语句的序列是一条接着一条编写的一组语句。一个序列中的每一条语句,都必须以相同的缩进开始。本节将会介绍条件式执行和迭代的控制语句。
条件语句
Python 的条件式语句的结构和其他语言中的条件式语句类似。关键词 if、elif 和else 是很重要的,冒号和缩进也同样重要。
单向的 if 语句的语法如下:
if <Boolean expression>:
<sequence of statements>
正如前面提到的,布尔表达式(Boolean expression)可以是任何的 Python 值,其中的一些值被当作是 False,另一些值被当作是 True。如果布尔表达式为 True,就运行该语句序列,否则,什么也不会发生。语句序列(一条或多条语句)必须至少按照一个空格或制
表符(通常一个制表符是 4 个空格)来缩进或对齐。冒号是唯一的分隔符,如果序列中只有一条语句,它可以紧跟在同一行的冒号之后。
双向的 if 语句的语法如下:
if <Boolean expression>:
<sequence of statements>
else:
<sequence of statements>
注意关键词 else 后面的冒号和缩进。这两个语句序列中,只有一个序列会运行。如果布尔表达式为 True,将会运行第 1 个序列;如果布尔表达式为 False,将会运行第 2个序列。
多向的 if 语句的语法如下:
if <Boolean expression>:
<sequence of statements>
elif <Boolean expression>:
<sequence of statements>
...
else:
<sequence of statements>
多向的 if 语句也只会运行一个语句序列。多向的 if 语句包含了一个或多个可选的布尔表达式,其中的每一个布尔表达式都跟在关键字 elif 后面。你可以忽略掉最后一个 else:子句。下面的示例是比较两个数的大小的问题,并输出正确的答案:
if x > y:
print("x is greater than y")
elif x < y:
print("x is less than y")
else:
print("x is equal to y")
使用 if name == "main"
前面讨论的 numberguess 程序包含了一个 main 函数的定义和如下的 if 语句:
if __name__ == "__main__":
main()
这条 if 语句的作用是,允许程序员要么将模块当作一个独立的程序运行,要么从 shell或另一个模块中导入它。其工作方式如下。每个 Python 模块都包含一组内建的模块变量,当加载该模块的时候,Python 虚拟机会自动为这些变量赋值。
如果该模块是作为一个独立的程序加载(要么通过从一个终端提示符来运行它,要么从一个 IDLE 窗口加载它),该模块的name变量会设置为字符串"main"。否则,这个变量会设置为模块的名称,在这个例子中,也就是"numberguess"。不管怎么样,该变量的赋值都会在模块中的任何代码加载之前完成。因此,当执行到了模块末尾的 if 语句的时候,只有当模块是作为一个独立的程序启动的时候,才会调用模块的 main 函数。
循环语句
Python 的 while 循环语句的结构,类似于其他语言中的循环结构。其语法如下:
while <Boolean expression>:
<sequence of statements>
下面的示例是计算从 1 到 10 的乘积并输出结果:
product = 1
value = 1
while value <= 10:
product *= value
value += 1
print(product)
注意,这里使用了扩展的赋值运算符*=。该行代码等价于如下的代码:
product = product * value
Python 包含了一个for 循环语句,以便更简洁地遍历值的一个序列。这条语句的语法如下:
for <variable> in <iterable object>:
<sequence of statements>
当运行这个循环的时候,它将可迭代的对象(iterable object)中包含的每一个值都赋值给循环变量,并且在每一个赋值上下文中运行语句的序列。可迭代对象的示例是字符串和列表。下面的代码段使用了 Python 的 range 函数返回整数的一个可迭代的序列,从而计算
前面展示的乘积。
product = 1
for value in range(1, 11):
product *= value
print(product)
Python 程序员通常更喜欢使用一个 for 循环来遍历确定的范围的值或值的序列。当继续循环的条件是一个任意的布尔表达式的时候,程序员则使用 while 循环。
3字符串及其运算
和其他语言一样,Python 字符串也是一个复合对象,它包含了其他对象,也就是字符。然而,Python 中的每个字符,本身也是一个单个的字符的字符串,并且在字面上也采用了和字符串相似的方式来表示。Python 的字符串类型名为 str,其中包含了很多的运算,本节将会介绍其中的一些。
运算符
当使用比较运算符来比较字符串的时候,将会使用 ASCII 的顺序来比较两个字符串中的每一个位置的成对的字符。因此,"a"小于"b",而"A"小于"a"。
+运算符构建并返回一个新的字符串,它包含了两个运算数中的字符。
在下标运算符的最简单的形式中,它期待范围是从0到字符串的长度减去1的一个整数。该运算符返回在字符串中该位置的字符。因此:
"greater"[0] # Returns "g"
尽管字符串索引不能超过其长度减去 1,但是,负的索引是允许的。
当索引为负值的时候,Python 会将这个值和字符串的长度相加,以确定要返回的字符的位置。在这种情况下,所提供的负的索引值,不能够比字符串长度的负值还要小。
字符串是不可变的,也就是说,一旦创建了字符串,不能更改其内部的内容。因此,不能使用下标来替换字符串中一个给定位置的字符。
下标的一种变体,叫作切片(slice)运算符,可以用它来获取一个字符串的子字符串。
切片的语法如下:
<a string>[<lower>:<upper>]
如果给出了<lower>值的话,它的范围是从 0 到字符串的长度减去 1 的一个整数。如果给出了<upper>的话,它的范围是从 0 到字符串的长度的一个整数。
当这两个值都省略的时候,切片会返回整个字符串。当省略了第 1 个值的时候,切片会返回从字符串的第 1 个字符开始的一个子字符串。当省略了第 2 个值的时候,切片会返回到字符串的最后一个字符结束的一个子字符串。
否则,切片会返回从较低的索引值开始,到较高的索引值减去 1 结束的一个子字符串。
如下是使用切片运算的一些示例:
"greater"[:] # Returns "greater"
"greater"[2:] # Returns "eater"
"greater"[:2] # Returns "gr"
"greater"[2:5] # Returns "eat"
对象和方法调用
除了标准的运算符和函数,Python 还包含了大量的可以操作对象的方法。方法和函数类似,方法也接受参数,执行一项任务并且返回一个值。然而,方法总是在相关联的对象之上调用。调用一个方法的语法如下所示:
<object>.<method name>(<list of arguments>)
如下是在字符串上调用方法的一些示例:
"greater".isupper() # Returns False
"greater".upper() # Returns "GREATER"
"greater".startswith("great") # Returns True
如果你试图运行一个对象所不识别的方法,Python 会抛出一个异常并且终止程序。想搞清楚在对象上能够识别一组什么样的方法,可以在 Python shell 中以该对象类型为参数来运行 Python 的 dir 函数。例如,dir(str)返回了字符串对象所识别的方法的一个列表。运行 help(str.upper)则会输出和 str.upper 方法用法相关的文档。
有些方法名,例如add和len,当 Python 看到一个对象和某种运算符或函数
一起使用的时候,就会运行这些方法。例如:
len("greater") # Is equivalent to "greater".__len__()
"great" + "er" # Is equivalent to "great".__add__("er")
"e" in "great" # Is equivalent to "great".__contains__("e")
4内建 Python 集合及其操作
列表
列表(list)是零个或多个 Python 对象的一个序列,这些对象通常称为项(item)。列表有一个字面的表示法,就是使用方括号将用逗号隔开的项括起来。下面是列表的一些示例:
[] # An empty list
["greater"] # A list of one string
["greater", "less"] # A list of two strings
["greater", "less", 10] # A list of two strings and an int
["greater", ["less", 10]] # A list with a nested list
和字符串一样,可以使用标准运算符对列表进行切片和连接。然而,在这种情况下,返回的结果也是列表。和字符串不同,列表是可变的,这意味着,可以替换、插入或删除列表中所包含的项。这一功能带来两个后果。首先,切片和连接运算符所返回的列表是新的列表,而不是最初列表的一部分。其次,列表类型包含了几个叫作修改器(mutator)的方法,它们的作用是修改一个列表的结构。可以在 Python shell 中输入 dir(list)来查看这些方法。
最常用的列表修改器方法是 append、insert、pop、remove 和 sort。如下是使用
这些方法的一些示例:
testList = [] # testList is []
testList.append(34) # testList is [34]
testList.append(22) # testList is [34, 22]
testList.sort() # testList is [22, 34]
testList.pop() # Returns 22; testList is [34]
testList.insert(0, 22) # testList is [22, 34]
testList.insert(1, 55) # testList is [22, 55, 34]
testList.pop(1) # Returns 55; testList is [22, 34]
testList.remove(22) # testList is [34]
testList.remove(55) # raises ValueError
字符串方法 split 和 join 分别从一个字符串中提取单词的一个列表并将单词列表组合到一起以形成一个字符串:
"Python is cool".split() #Returns[’Python’,’is’,’cool’]
" ".join(["Python","is","cool"]) #Returns’Python is cool’
元组
元组(tuple)是项的一个不可变的序列。元组字面值用圆括号将各项括起来,并且元组必须至少包含两个项。元组实际上就像列表一样,只不过它没有修改器方法。要了解元组的可用方法,在 Python shell 中运行 dir(tuple)。
遍历序列
for 循环用来遍历一个序列(如字符串、列表或元组)中的项。例如,如下的代码段输出一个列表中的项:
testList = [67, 100, 22]
for item in testList:
print(item)
如下的代码是基于索引的遍历方法,显然上面的代码与其等价但是更简单一些:
testList = [67, 100, 22]
for index in range(len(testList)):
print(testList[index])
字典
字典(dictionary)包含零个或多个条目。每个条目(entry)都将一个唯一的键和一个值相关联。键通常是字符串或整数,而值是任何的 Python 对象。
字典字面值将键—值条目包含到一对花括号中,如下面的示例所示:
{} # An empty dictionary
{"name":"Ken"} # One entry
{"name":"Ken", "age":61} # Two entries
{"hobbies":["reading", "running"]} # One entry, value is a list
dic = {"name":"Ken", "age":61}
for key,value in dic.items():
print(key, value)
我们使用下标运算符来访问一个给定键的值,给一个新键添加一个值,以及替换给定键的值。pop 方法删除条目,并且返回给定键的值。keys 方法返回键的一个可迭代的对象,而 values 方法返回值的一个可迭代的对象。for 循环可以遍历一个字典的键。
搜索一个值
程序员可以对值和集合使用 in 运算符,从而在字符串、列表、元组或字典中搜索一个值。这个运算符返回 True 或 False。字典搜索的目标值应该是一个潜在的键。当获知给定的值在一个序列(字符串、列表、元组)中时,index 方法将会返回第一次搜索到的这个值的位置。对于字典来说,get 和 pop 方法可以接受两个参数,一个键和一个默认值。搜索失败的话,将会返回该默认值;搜索成功的话,将会返回和该键相关联的值。
5 函数
函数定义
Python 函数的定义语法是:
def <function name>(<list of parameters>):
<sequence of statements>
命名函数和参数的规则和惯例,与命名变量的是相同的。必需参数的列表可以为空,或者可以包含用逗号隔开的名称。和其他的编程语言不同,这里没有和参数名称或函数名自身相关联的数据类型。
如下是一个简单的函数,它计算并返回一个数字的平方:
def square(n):
"""Returns the square of n."""
result = n ** 2
return result
注意,在函数头的下方使用了带有 3 个引号的字符串。这是一个文档字符串(docstring)。这个字符串就像是函数中的注释一样,当用户在Python shell提示符窗口输入help(square)的时候,将会显示这个字符串。你所定义的每一个函数,都应该包含一个文档字符串,以说明这个函数做什么,并且给出与参数或返回值相关的任何信息。
函数可以引入新的变量,叫作临时变量(temporary variable)。在 square 函数中,n 是参数,result 是一个临时变量。函数的参数和临时变量,只有在函数调用的生命周期中存在,并且对其他的函数或外围程序是不可见的。因此,几个不同的函数可以使用相同的参数和变量名而不会导致冲突。
当一个函数并不包含 return 语句的时候,它在执行完最后一条语句的时候,会自动地返回一个 None 值。在模块中,可以按照任意的顺序来定义函数,只要在函数的定义被编译之前,不会真正地去执行函数就可以了。下面的示例展示了在模块开始处的一个非法的函数调用:
first() # Raises a NameError (function undefined yet)
def first():
second() # Not an error, because not actually
print("Calling first.") # called until after second is defined
def second():
print("Calling second.")
first() # Here is where the call should go
当 Python 运行第一行代码的时候,函数 first 还没有定义,因此,会抛出一个异常。如果在这行代码的开始处放置一个注释符号#,并且再次运行这段代码,程序将会运行并正常结束。在这个例子中,即便函数 second 似乎在定义它之前就调用了,但实际上直到调用函数 first 的时候,才真正地调用了 second,而这个时候,这两个函数都已经定义过了。
可以使用<parameter name> = <default value>的形式,带上为参数指定的默认值,从而将参数指定为可选的。在参数列表中,必需的参数(那些不带默认值的)必须要放在可选的参数之前。
递归函数
递归函数是调用自身的函数。为了防止函数无限次地重复调用自己,它必须至少包含一条选择语句。这条语句检查一个叫作基本条件(base case)的条件,来判断是停止还是继续递归的步骤。
我们来看看如何把一个迭代算法转换为一个递归函数。如下是一个名为 displayRange的递归函数的定义,它输出从下边界到上边界的数字:
def displayRange(lower, upper):
"""Outputs the numbers from lower to upper."""
while lower <= upper:
print(lower)
lower = lower + 1
如何将这个函数转换为递归函数呢?首先,需要注意两点重要的情况:
- 当 lower<=upper 的时候,循环体继续执行;
- 当函数执行的时候,lower 增加 1,但是 upper 不变。
等价的递归函数可以执行类似的基本操作,但是,用一条 if 语句替代了循环,用函数的递归调用替代了赋值语句。如下是做出这些修改后的代码:
def displayRange(lower, upper):
"""Outputs the numbers from lower to upper."""
if lower <= upper:
print(lower)
displayRange(lower + 1, upper)
尽管两个函数的语法和设计是不同的,但它们所执行的算法过程是相同的。递归函数的每次调用,都会按照顺序访问下一个数,这和函数的迭代版本中的循环所做的事情是一样的。
大多数递归函数都至少接受一个参数。这个数据值测试结束递归过程的基本条件。在每一个递归步骤之前,也会以某种方式修改这个值。每次对数据值的修改,都应该会产生一个新的数据值,以允许函数最终达到基本条件。在 displayRange 的例子中,也就是在每一次递归调用之前,将参数 lower 的值增加 1,以使得其最终能够超过参数 upper 的值。
下面是递归函数的另一个示例,它生成并返回一个值。Python 的 sum 函数接受数字的一个集合,并且返回它们的加和。这个函数应该会返回从下界到上界的数字的加和。如果lower 超过了 upper(基本条件),递归函数 ourSum 会返回 0。否则的话,该函数会把lower 和针对 lower+1 与 upper 执行 ourSum 的结果相加,并且返回最终的结果:
def ourSum(lower, upper):
"""Returns the sum of the numbers from lower thru upper."""
if lower > upper:
return 0
else:
return lower + ourSum(lower + 1, upper)
对 ourSum 的递归调用,会将数字从 lower+1 一直加到 upper。然后,这个函数把lower 和结果相加,并将其返回。
类和对象
类描述了数据以及和一组对象相关的方法。类提供了一个蓝图,可以用来创建对象以及在这些对象上调用方法的时候所执行的代码。Python 中所有的数据类型都是类。
定义 Python 类的语法如下:
def <class name>(<parent class name>):
<class variable assignments>
<instance method definitions>
按照惯例,类名是要大写的。类定义的代码通常放在模块中,该模块的文件名和类名一样,但是是小写的。相关的类也可以出现在同一模块中。父类名是可选的,在这种情况下,会假设它是 object。所有的 Python 类都位于层级之中,object 在这个层级的根部。有几个方法如str和eq均定义于 object 中并且是所有的子类会自动继承的。实例方法在类的对象之上运行,它们包含了访问或修改实例变量的代码。实例变量引用了单个对象所拥有的存储。类变量引用了类的所有对象共同拥有的存储。
为了说明这些思路,本节现在将定义一个 Counter 类的代码。正如其名,Counter 对象用来记录整数。计数器的值最初为 0,并且可以随时将其重新设置为 0。我们可以递增或递减一个计数器,获取其当前的整数值,获取其字符串表示,或者比较两个计数器是否相等。
该类的代码如下:
"""
File: counter.py
Defines a Counter class for counting.
"""
class Counter(object):
"""Models a counter."""
# Class variable
instances = 0
#Constructor
def __init__(self):
"""Sets up the counter."""
Counter.instances += 1
self.reset()
# Mutator methods
def reset(self):
"""Sets the counter to 0."""
self._value = 0
def increment(self, amount = 1):
"""Adds amount to the counter."""
self._value += amount
def decrement(self, amount = 1):
"""Subtracts amount from the counter."""
self._value -= amount
# Accessor methods
def getValue(self):
"""Returns the counter's value."""
return self._value
def __str__(self):
"""Returns the string representation of the counter."""
return str(self._value)
def __eq__(self, other):
"""Returns True if self equals other
or False otherwise."""
if self is other: return True
if type(self) != type(other): return False
return self._value == other._value
如下是在 Python shell 中和一些 Counter 对象的交互代码:
>>>from counter import Counter
>>>c1=Counter()
>>>print(c1)
0
>>>c1.getValue()0
>>>str(c1)
’0’
>>>c1.increment()
>>>print(c1)
1
>>>c1.increment(5)
>>>print(c1)
6
>>>c1.reset()
>>>print(c1)
0
>>>c2=Counter()
>>>Counter.instances
2
>>>c1==c1
True
>>>c1==0
False
>>>c1==c2
True
>>>c2.increment()
>>>c1==c2
False
>>>
现在我们来简单介绍一下,Counter 类是 object 的一个子类。
类变量 instances 记录了所创建的 Counter 对象的数目。除了最初在通过赋值引入类变量的时候之外,在其他时候,类变量必须有一个类名作为其前缀。定义实例方法的语法,和定义函数的语法是相同的。然而,有一个叫作 self 的、额外的参数,它总是出现在参数列表的开始处。在一个方法定义的上下文中,self 引用的是在其上运行该方法的那个对象。
实例方法init也叫作构造方法,当创建 Counter 的实例的时候,会自动运行其构造方法。该方法初始化了实例变量并且更新了类变量。注意,init使用了 self.reset()的语法,调用了实例方法 reset,以初始化单个的实例变量。其他的实例方法分为两类:修改器(mutator)方法和访问器(accessor)方法。修改器方法通过修改对象的实例变量,来修改或改变对象的内部状态。访问器方法直接查看或使用对象的实例变量的值,而不会修改它们。
在初次调用实例方法 reset 的时候,它引入了实例变量 self.value。随后,对该方法的任何其他的调用,都会将这个实例变量的值修改为 0。一个实例变量总是有一个 self前缀。与参数或临时变量不同,实例变量在类的任何方法之中都是可见的。一些类的实例变量拼写为带有一个字符前缀。这个惯例有助于读者将其与参数以及临时变量
区分开来,并且这也不鼓励代码编写者在类定义之外访问实例变量。
increment 和 decrement 方法使用默认的参数,这允许程序员来选择指定还是不指定这个量。Counter 类中的str方法覆盖了 object 类中的相同的方法。当把对象作为参数传递给 str 函数的时候,python 在该对象上运行str。当在一个对象上运行一个方法的时候,Python 首先在该对象自己的类中查找该方法的代码。如果没有在那里找到这个方法,Python 会在其父类中查找,依次类推。如果最终没有找到该方法的代码(在 object类中查找完了之后),Python 会抛出一个异常。
当 Python 的 print 函数接受参数的时候,会自动运行该参数的str方法,获取其字符串表示以用于输出。我们鼓励程序员为每一个自定义的类都包含一个str方法,以帮助进行调试。
当 Python 看到==的时候,它会运行eq方法。该方法默认地定义在 object 类中,它运行 is 运算符,该运算符会比较两个运算数的对象相等性。正如你所看到的,只要两个不同的 counter 对象拥有相同的值,我们就让视作它们是相等的。因为==的第二个运算数
可以是任何的对象,eq方法在访问它们的实例变量之前,会询问运算数的类型是否是相同的。注意,我们在对象上使用了点表示法来访问实例变量。
文件操作
文本文件的输出
根据文本文件的格式和数据的用途,我们可以将文本文件中的数据看做是字符、单词、数字或者文本行。当把这些数据当作整数或浮点数的时候,必须用空白字符(空格、制表符和换行符)将其分隔开。例如,当使用一个文本编辑器查看包含 6 个浮点数的一个文本文件的时候,如下所示:
34.6 22.33 66.75
77.12 21.44 99.01
注意,该格式用空格或换行作为文本中的各项的分隔符。文本文件的所有数据输出或数据输入都必须是字符串。因此,在输出之前,这些数字必须先转换为字符串,并且在输入之后,这些字符串必须再转换为数字。可以使用文件对象将数据输出到文本文件中。Python 的 open 函数接受文件的路径名称和模式字符串作为参数,它打开到磁盘上的文件的一个连接,并且返回文件对象。模式字符串’r’表示输入文件,’w’表示输出文件。因此,如下的代码在名为 myfile.txt的文
件上打开一个文件对象以进行输出:
>>>f=open("myfile.txt",’w’)
如果该文件并不存在,会使用给定的路径名称来创建它。如果该文件已经存在,Python 会打开它。当数据写入到文件并且文件关闭的时候,文件中之前存在的任何数据都将会被擦除。我们使用 write 方法和文件对象,将字符串数据写入(或输出)到一个文件之中。write
方接受单个的字符串参数。如果你想要输出文本并且以换行来结束,必须在字符串中包含转义字符\n。下面的语句将两行文本写入到了文件中:
>>> f.write("First line.\nSecond line.\n")
当所有的输出完成之后,应该使用 close 方法来关闭文件,如下所示:
>>> f.close()
如果没有能够成功地关闭输出的文件,将会导致数据丢失。
将数字写入到一个文本文件
文件方法 write 接受字符串作为参数。因此,其他类型的数据,例如整数或浮点数,必须先转换成字符串,然后才能写入到输出文件中。在 Python 中,大多数数据类型的值都可以使用 str 函数来转换为字符串。然后,使用空格或换行作为分隔符字符,将所得到的字符串写入到文件中。
下面的代码段展示了如何将整数输出到文本文件中。生成 1 到 500 之间的 500 个随机的整数,并将其写入到名为 integers.txt 的文本文件中。换行字符作为分隔符。
import random
f=open("integers.txt",'w')
for count in range(500):
number = random.randint(1, 500)
f.write(str(number) + "\n")
f.close()
从文本文件读取文本
打开一个文件进行输入的方式,和打开一个文件进行输出的方式相同。唯一的变化在于模式字符串。在打开文件进行输入的情况下,使用’r’字符串。然而,如果路径名是从当前的工作目录无法访问的,Python 会产生错误。打开 myfile.txt 文件进行输入的代码如下:
>>>f=open("myfile.txt",’r’)
有几种方法可以实现从输入文件读取数据。最简单的方法是使用文件方法 read 来将整个文件的内容作为一个单独的字符串输入。如果文件包含了多行文本的话,换行字符将会嵌入到这个字符串中。下面的这个 shell 会话展示了如何使用 read 方法:
>>> text=f.read()
>>> text
’First line. \nSecond line. \n’
>>>print(text)
First line.
Second line.
>>>
在输入完成后,再次调用 read 将会返回一个空的字符串,表明已经到达了文件的末尾。要重复输入,必须再次打开文件。这样的话,就不必关闭该文件了。此外,一个应用程序可能每次只读取和处理一行文本。一个 for 循环就可以很好地完成这一任务。这个 for 循环将一个文件对象当作是文本行的一个序列。在每次通过循环的时候,循环变量都跳到序列中的下一行文本。如下的会话重新打开示例文件并访问其中的每一行文本:
>>>f=open("myfile.txt",’r’)
>>>for line in f:
print(line)
First line.
Second line.
>>>
注意,print 似乎输出了一个额外的换行。这是因为从文件输入的每一行文本,都包含一个换行字符。当你想要从一个文件读取特定数目的行的时候(例如,只读取第 1 行),可以使用文件方法 readline。readline 方法只读取一行的输入并且返回该字符串,包括换行符。如果 readline 遇到了文件的末尾,它会返回空字符串。下面的代码段使用一个 while True循环并用 readline 输入所有的文本行:
>>>f=open("myfile.txt",’r’)
>>> while True:
line = f.readline()
if line == "":
break
print(line)
First line.
Second line.
>>>