本文为个人学习笔记,仅作为个人学习用途,如有侵权,联系我删除。
对于一个简单的hello_world.py程序,Python所做的工作也相当多。
运行文件hello_world.py时,末尾的.py指出这是一个python程序,因此编辑器将使用python解释器来运行它。python解释器读取整个程序,确定其中每个单词的含义。例如,看到单词print
时,解释器就会将括号中的内容打印到屏幕,而不会管括号中的内容是什么。
变量使用的规则:
变量名只能包含字母、数字、下划线。不能是数字开头。
变量名不能包含空格,可以使用下划线来分隔其中的单词
不要将Python关键字和函数名用做变量名,比如print
变量名应该既简短又具有描述性。例如,name
比 n
好, student_name
比 s_n
好,name_length
比 length_of_persons_name
好。(这样说来yolo_v4代码里的变量名就很不好)
慎用小写字母l
和大写字母O
,因为很容易被错看成1和0
就目前而言,应该使用小写的变量名。在变量中使用大写字母虽然不会导致错误,但避免使用大写字母是个不错的主意。
程序员都会犯错,而且大多数程序员每天都会犯错。虽然优秀的程序员也会犯错,但他们知道如何高效地消除错误。
实际上,这一节介绍了一个典型的traceback。带我们学习如何看懂traceback
traceback是一个记录,指出了解释器尝试运行代码时,在什么地方陷入了困境。
Traceback (most recent call last):
File "hello_world.py", line 2, in # 1
print(mesage) # 2
NameError: name 'mesage' is not defined # 3
解释器指出,文件hello_world.py的第2行存在错误,见1
它列出了这行代码,旨在帮助你快速找出错误,见2
它还指出了它发现了什么样的错误,见3
很多编程错误都很简单,只是在程序的某一行输错了一个字符。为找出这种错误而花费很长时间的大有人在。很多程序员天资聪颖、经验丰富,却为找出这种细微的错误花费数小时。你可能觉得这很好笑,但别忘了,在你的编程生涯中,经常会有同样的遭遇。
要理解新的编程概念,最佳的方式是尝试在程序中使用它们。如果你在做本书的练习时陷入了困境,请尝试做点其他的事情。如果这样做后依然无法摆脱困境,请复习相关内容。如果这样做后情况依然如故,请参阅附录C的建议。
字符串就是一系列字符。在Python中,用引号括起的都是字符串。 引号可以是双引号,也可以是单引号。
`name = “ADA lovelace”
以首字母大写的方式显示每个单词,即将每个单词的首字母都改为大写,其余字母都改为小写。
将字符串全部改为大写
将字符串全部改为小写
name = 'ADA lovelace'
print(name.title())
print(name.upper())
print(name.lower())
Python使用+
来合并字符串,将两个或字符串拼接成一个。
first_name = "ada"
last_name = "lovelace"
full_name = first_name + " " + last_name
message = "Hello, " + full_name.title() + "!"
print(message)
+
进行字符串拼接的话,拼接的地方不会出现空格空白:泛指任何非打印字符,如空格、制表符和换行符。
在字符串中使用制表符和换行符,使用:
制表符:\t
换行符:\n
print("Python")
print("\tpython")
print("\npython")
print("\n\tpython")
对程序来说,'python'
和 'python '
是两个不同的字符串。
Python能够找出字符串开头和末尾多余的空白。上一节提到,换行符\n
也是一种空白
favorite_language = 'python '
去掉字符串末尾的空白。r即right
return a copy of the string with trailing whitespace removed,因此favorite_language没有被改变
去掉字符串开头的空白。l即left
同时去掉字符串两端的空白
favorite_language = ' python '
favorite_language.rstrip()
favorite_language.lstrip()
favorite_language.strip()
语法错误是指程序中包含非法的Python代码。
下面演示了如何正确地使用单引号和双引号:
撇号需要位于两个双引号之间
message = "One of Python's strenght is its diverse and supportive community."
print(message)
双引号中可以套单引号,单引号中可以套双引号
print("The language 'Python' is named after Monty Python, not the snake.")
'I told my friend, "Python is my favorite language!"'
Python中,可对整数执行加(+
)减(-
)乘(*
)除(/
)运算。
浮点数即带小数点的数。”浮点“指出了这一个事实:小数点可出现在数字的任何位置。
>>> 0.1 + 0.1
0.2
>>> 0.2 + 0.2
0.4
>>> 2 * 0.1
0.2
>>> 2 * 0.2
0.4
需要注意的是,结果包含的小数位数可能是不确定的。所有语言都存在这种问题,没有什么可担心的。就现在而言,暂时忽略多余的小数位数即可
>>> 0.2 + 0.1
0.30000000000000004
>>> 3 * 0.1
0.30000000000000004
函数str()可以让Python将非字符串值表示为字符串
str(23)
Python中,注释用#
标识。
该编写什么样的注释呢?
编写注释的主要目的是阐述代码要做什么,以及是如何做的。在开发项目期间,你对各个部分如何协同工作了如指掌,但过段时间后,有些细节你可能不记得了。当然,你总是可以通过研究代码来确定各个部分的工作原理,但通过编写注释,以清晰的自然语言对解决方案进行概述,可节省很多时间。
要成为专业程序员或与其他程序员合作,就必须编写有意义的注释。当前,大多数软件都是合作编写的,编写者可能是同一家公司的多名员工,也可能是众多致力于同一个开源项目的人员。训练有素的程序员都希望代码中包含注释,因此你最好从现在开始就在程序中添加描述性注释。
作为新手,最值得养成的习惯之一是,在代码中编写清晰、简洁的注释。
‘Zen of Python’
>>> import this
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Python程序员笃信代码可以编写得漂亮而优雅。编程是要解决问题的,设计良好、高效而漂亮的解决方案都会让程序员心生敬意。随着你对Python的认识越来越深入,并使用它来编写越来越多的代码,有一天也许会有人站在你后面惊呼:“哇,代码编写得真是漂亮!”
Simple is better than complex.
如果有两个解决方案,一个简单,一个复杂,但都行之有效,就选择简单的解决方案吧。这样,你编写的代码将更容易维护,你或他人以后改进这些代码时也会更容易。
Complex is better than complicated.
现实是复杂的,有时候可能没有简单的解决方案。在这种情况下,就选择最简单可行的解决方案吧。
Readability counts.
即便是复杂的代码,也要让它易于理解。开发的项目涉及复杂代码时,一定要为这些代码编写有益的注释。
There should be one-- and preferably only one --obvious way to do it.
如果让两名Python程序员去解决同一个问题,他们提供的解决方案应大致相同。这并不是说编程没有创意空间,而是恰恰相反!然而,大部分编程工作都是使用常见解决方案来解决简单的小问题,但这些小问题都包含在更庞大、更有创意空间的项目中。在你的程序中,各种具体细节对其他Python程序员来说都应易于理解。
Now is better than never.
你可以将余生都用来学习Python和编程的纷繁难懂之处,但这样你什么项目都完不成。 不要企图编写完美无缺的代码;先编写行之有效的代码,再决定是对其做进一步改进,还是转而去编写新代码。
等你进入下一章,开始研究更复杂的主题时,务必牢记这种简约而清晰的理念。如此,经验丰富的程序员定将对你编写的代码心生敬意,进而乐意向你提供反馈,并与你合作开发有趣的项目。
列表让你能够在一个地方存储成组的信息.列表是新手可直接使用的最强大的Python功能之一,它融合了众多重要的编程概念。
列表由一系列按特定顺序排列的元素组成。在Python中,用[]
来表示列表,并用逗号来分隔其中的元素。
bicycles = ['trek', 'cannondale', 'redline', 'specialized']
print(bicycles)
如果你直接让Python将列表打印出来,Python将打印列表的内部表示,包括方括号:
['trek', 'cannondale', 'redline', 'specialized']
这不是你要让用户看到的输出,因此下面来学习如何访问列表元素
列表是有序集合,因此要访问列表的任何元素,只需将该元素的位置或索引告诉Python即可。要访问列表元素,可指出列表的名称,再指出元素的索引,并将其放在方括号内。
bicycles = ['trek', 'cannondale', 'redline', 'specialized']
print(bicycles[0])
bicycles = ['trek', 'cannondale', 'redline', 'specialized']
print(bicycles[0].title())
在Python中,第一个列表元素的索引为0,而不是1。Python为访问最后一个列表元素提供了一种特殊语法。通过将索引指定为-1,可让Python返回最后一个列表元素:
bicycles = ['trek', 'cannondale', 'redline', 'specialized']
print(bicycles[0])
print(bicycles[-1])
这种约定也适用于其他负数索引,例如,索引-2返回倒数第二个列表元素,索引-3返回倒数第三个列表元素,以此类推。
可像使用其他变量一样使用列表中的各个值。
bicycles = ['trek', 'cannondale', 'redline', 'specialized']
message = "My first bicycle was a " + bicycles[0].title() + '.'
print(message)
要修改列表元素,可指定列表名和要修改的元素的索引,再指定元素的新值。
motorcycles = ['honda', 'yamaha', 'suzuki']
print(motorcycles)
motorcycles[0] = 'ducati'
print(motorcycles)
Python提供了多种在既有列表中添加新数据的方式。
motorcycles = ['honda', 'yamaha', 'suzuki']
print(motorcycles)
motorcycles.append('ducati')
print(motorcycles)
方法append()将元素’ducati’添加到了列表末尾,而不影响列表中的其他所有元素。
方法append()让动态地创建列表易如反掌,例如,你可以先创建一个空列表,再使用一系列的append()语句添加元素。
motorcycles = []
motorcycles.append('honda')
motorcycles.append('yamaha')
motorcycles.append('suzuki')
print(motorcycles)
使用方法insert()可在列表的任何位置添加新元素。为此,需要指定新元素的索引和值。
motorcycles = ['honda', 'yamaha', 'suzuki']
motorcycles.insert(0, 'ducati')
print(motorcycles)
方法insert()在索引0处添加空间,并将值’ducati’存储到这个地方。这种操作将列表中既有的每个元素都右移一个位置
你可以根据位置或值来删除列表终的元素、
如果知道要删除的元素在表中的位置,可使用del语句。
motorcycles = ['honda', 'yamaha', 'suzuki']
print(motorcycles)
del motorcycles[1]
print(motorcycles)
使用del语句删除列表元素的条件是知道其索引。
使用del语句将值从列表中删除后,你就无法访问它了。但是,有时候,你要将元素从列表中删除,同时要使用你所删除的值。
方法pop()可删除列表末尾的元素,并让你接着使用它。pop即弹出,这个术语源自这样的类比:列表就像一个栈,而删除列表末尾的元素相当于弹出栈顶元素。
motorcycles = ['honda', 'yamaha', 'suzuki']
print(motorcycles)
poped_motorcycle = motorcycles.pop()
print(motorcycles)
print(poped_motorcycle)
实际上,你可以用pop()来删除列表中任何位置的元素,只需在括号中指定要删除的元素的索引即可。
motorcycles = ['honda', 'yamaha', 'suzuki']
first_owned = motorcycles.pop(0)
print('The first motorcycle I owned was a ' + first_owned.title() + '.')
如果你要从列表中删除一个元素,且不再以任何方式使用它,就使用del语句;如果你要在删除元素后还能继续使用它,就使用方法pop()。
有时候,你不知道要从列表中删除的值所处的位置。如果你只需要删除元素的值,可用方法remove().
motorcycles = ['honda', 'yamaha', 'suzuki', 'ducati']
print(motorcycles)
motorcycles.remove('yamaha')
print(motorcycles)
使用remove()从列表中删除元素时,也可以接着使用它的值。
motorcycles = ['honda', 'yamaha', 'suzuki', 'ducati']
print(motorcycles)
too_expensive = 'ducati'
motorcycles.remove(too_expensive)
print('A ' + too_expensive + ' is too expensive.')
注意:
方法remove()只删除第一个指定的值。如果要删除的值可能在列表中出现多次,就需要
使用循环来判断是否删除了所有这样的值。
我们经常需要以特定的顺序呈现信息。有时我们需要保留列表元素最初的排列顺序,有时有需要调整列表的排列顺序。Python提供了很多组织列表的方式。
Python的sort()方法可以按字母顺序排列列表。
cars = ['bmw', 'audi', 'toyota', 'subaru']
cars.sort()
print(cars)
方法sort()永久性地修改了列表元素的排列顺序。
我们还可以按与字母顺序相反的顺序排列列表元素,为此,只需向sort()方法传递参数
reverse=True。
cars = ['bmw', 'audi', 'toyota', 'subaru']
cars.sort(reverse = True)
print(cars)
要保留列表元素原来的排列顺序,同时以特定的顺序呈现它们,可使用函数sorted()。
cars = ['bmw', 'audi', 'toyota', 'subaru']
print('here is the original list:', '\n', cars, '\n')
print('here is the sorted function:', '\n', sorted(cars), '\n')
print('here is the original list again:', '\n', cars, '\n')
调用函数sorted()后,列表元素的排列顺序并没有变。如果要按与字母顺序相反的顺序显示列表,也可向函数sorted()传递参数reverse=True。
注意:注意 在并非所有的值都是小写时,按字母顺序排列列表要复杂些。决定排列顺序时,有多种解读大写字母的方式。
要反转列表的排列顺序,使用方法reverse():
cars = ['bmw', 'audi', 'toyota', 'subaru']
cars.reverse()
print(cars)
cars.reverse()
print(cars)
reverse()永久地修改列表中元素的排列顺序。但是可以随时回到原来的排列顺序,再次调用reverse()即可
使用函数len()可快速获悉列表的长度
cars = ['bmw', 'audi', 'toyota', 'subaru']
len(cars)
发生索引错误却找不到解决办法时,请尝试将列表或其长度打印出来。列表可能与你以
为的截然不同,在程序对其进行了动态处理时尤其如此。通过查看列表或其包含的元素
数,可帮助你找出这种逻辑错误。
magicians = ['alice', 'david', 'carolina']
for magician in magicians:
print(magician)
magicians = ['alice', 'david', 'carolina']
for magician in magicians:
print(magician.title() + ', that was a great trick.')
当你开始编写必须正确缩进的代码时,需要注意一些常见的缩进错误。例如,有时候,程序员会将不需要缩进的代码块缩进,而对于必须缩进的代码块却忘了缩进。
for语句末尾的冒号告诉Python,下一行是循环的第一行。如果你不小心遗漏了冒号,将导致语法错误,因为Python不知道你意欲何为
Python函数range()让你能够轻松地生成一系列的数字
for value in range(1,5):
print(value)
注意:
函数range()让Python从你指定的第一个值开始数,并在到达你指定的第二个值后停止,因此输出不包含第二个值(这里为5)。
可以使用函数list()将range()的结果直接转换为列表,即将range()作为list()的参数
l = list(range(1, 6))
print(l)
在面向对象编程中,你编写表示现实世界中的事物和情景的类,并基于这些类来创建对象。编写类时,你定义一大类对象都有的通用行为。基于类创建对象时,每个对象都自动具备这种通用行为,然后可根据需要赋予每个对象独特的个性。使用面向对象编程可模拟现实情景,其逼真程度达到了令你惊讶的地步。
理解面向对象编程有助于你像程序员那样看世界,还可以帮助你真正明白自己编写的代码:不仅是各行代码的作用,还有代码背后更宏大的概念。了解类背后的概念可培养逻辑思维,让你能够通过编写程序来解决遇到的几乎任何问题。
下面来编写一个表示小狗的简单类Dog——它表示的不是特定的小狗,而是任何小狗。小狗都有名字和年龄;大多数小狗还会蹲下和打滚。我们的Dog类将包含它们。
这个类让Python知道如何创建表示小狗的对象。编写这个类后,我们将使用它来创建表示特定小狗的实例。
在Python中,首字母大写的名称指的是类。
这个类定义中的括号是空的,因为我们要从空白创建这个类。
class Dog():
"""一次模拟小狗的简单尝试"""
def __init__(self, name, age):
"""初始化属性name和age"""
self.name = name
self.age = age
def sit(self):
"""模拟小狗被命令时蹲下"""
print(self.name.title() + "is now sitting.")
def roll_over(self):
"""模拟小狗被命令时打滚"""
print(self.name.title() + "is rolled over!")
my_dog = Dog('willie', 6)
print("My dog's name is " + my_dog.name.title() + ".")
print("My dog's age is " + str(my_dog.age) + ".")
类中的函数称为方法;你前面学到的有关函数的一切都适用于方法,就目前而言,唯一重要的差别是调用方法的方式。init()是一个特殊的方法,每当你根据Dog类创建新实例时,Python都会自动运行它。在这个方法的名称中,开头和末尾各有两个下划线,这是一种约定,旨在避免Python默认方法与普通方法发生名称冲突。
我们将方法__init__()定义成了包含三个形参:self、name和age。在这个方法的定义中,形参self必不可少,还必须位于其他形参的前面。为何必须在方法定义中包含形参self呢?因为Python调用这个__init__()方法来创建Dog实例时,将自动传入实参self。每个与类相关联的方法调用都自动传递实参self,它是一个指向实例本身的引用,让实例能够访问类中的属性和方法。我们创建Dog实例时,Python将调用Dog类的方法__init__()。我们将通过实参向Dog()传递名字和年龄;self会自动传递,因此我们不需要传递它。每当我们根据Dog类创建实例时,都只需给最后两个形参(name和age)提供值。
以self为前缀的变量都可供类中的所有方法使用,我们还可以通过类的任何实例来访问这些变量。self.name = name获取存储在形参name中的值,并将其存储到变量name中,然后该变量被关联到当前创建的实例。self.age = age的作用与此类似。像这样可通过实例访问的变量称为属性。
Dog类还定义了另外两个方法:sit()和roll_over()。由于这些方法不需要额外的信息,如名字或年龄,因此它们只有一个形参self。我们后面将创建的实例能够访问这些方法,换句话说,它们都会蹲下和打滚。
类可以看作是如何创建实例的说明。 比如Dog类就是一个说明,让Python知道如何创建一个特定小狗的实例。
my_dog = Dog('willie', 6)
print("My dog's name is " + my_dog.name.title() + ".")
print("My dog's age is " + str(my_dog.age) + ".")
my_dog = Dog('willie', 6)
遇到这行代码时,Python使用实参'willie'
和6
调用Dog类中的方法__init__()
。
方法__init__()创建一个表示特定小狗的示例,并使用我们提供的值来设置属性name和age。方法__init__()并未显式地包含return语句,但Python自动返回一个表示这条小狗的实例。我们将这个实例存储在变量my_dog中
my_dog.name
我们使用句点表示法访问实例的属性。在这里,Python先找到实例my_dog,再查找与这个实例相关联的属性name。在Dog类中引用这个属性时,使用的是self.name。
my_dog.sit()
我们使用句点表示法来调用Dog类中定义的任何方法。即指定实例的名称(这里是my_dog)和要调用的方法,并用句点分隔它们。Python在类Dog中查找方法sit()并运行其代码。
问题:
def sit(self):
"""模拟小狗被命令时蹲下"""
print(self.name.title() + "is now sitting.")
sit()方法需要用到name,定义sit()时可以直接传入name吗?即def sit(self, name)
?
答:
不能。
因为实例化一个对象时,是调用构造方法__intit__(),that means,进行实例化时,提供的实参是传递给构造方法的,之后我们在构造方法
def __init__(self, name, age):
self.name = name
self.age = age
中,将形参name获得的实参值传递给属性。
而在:
def sit(self):
"""模拟小狗被命令时蹲下"""
print(self.name.title() + "is now sitting.")
类中的方法需要获得参数时,就从self.属性的渠道获得。
但是定义类中的方法时,方法是可以有除了self以外的形参的:
class Car():
...
def up_odo(self, mile):
pass
这里的形参,在实例化的对象调用这个方法时传入对应的实参,即
car = Car()
car.up_odo(500)
也就是说,定义类中的方法时,除了self以外的形参,与类的定义无瓜
类编写好后,你的大部分时间都将花在使用根据类创建的实例上。你需要执行的一个重要任务是修改实例的属性。你可以直接修改实例的属性,也可以编写方法以特定的方式进行修改。
下面来编写一个表示汽车的类,它存储了有关汽车的信息,还有一个汇总这些信息的方法:
class Car():
"""一次模拟汽车的简单尝试"""
def __init__(self, make, model, year):
"""初始化描述汽车的属性"""
self.make = make
self.model = model
self.year = year
def get_descriptive_name(self):
"""返回整洁的描述性信息"""
long_name = str(self.year) + ' ' + self.make + ' ' + self.model
return long_name.title()
my_new_car = Car('audi', 'a4', 2016)
print(my_new_car.get_descriptive_name())
我们定义了方法__init__()。与前面的Dog类中一样,这个方法的第一个形参为self;我们还在这个方法中包含了另外三个形参:make、model和year。方法__init__()接受这些形参的值,并将它们存储在根据这个类创建的实例的属性中。创建新的Car实例时,我们需要指定其制造商、型号和生产年份。
我们定义了一个名为get_descriptive_name()的方法,它使用属性year、make和model创建一个对汽车进行描述的字符串,让我们无需分别打印每个属性的值。为在这个方法中访问属性的值,我们使用了self.make、self.model和self.year。
我们根据Car类创建了一个实例,并将其存储到变量my_new_car中。接下来,我们调用方法get_descriptive_name(),指出我们拥有的是一辆什么样的汽车:
2016 Audi A4
接下来,给它添加一个随时间变化的属性,它存储汽车的总里程。
类中的每个属性都必须有初始值,哪怕这个值是0或空字符串。在有些情况下,如设置默认值时,在方法__init__()内指定这种初始值是可行的;如果你对某个属性这样做了,就无需包含为它提供初始值的形参。
下面来添加一个名为odometer_reading的属性,其初始值总是为0。我们还添加了一个名为read_odometer()的方法,用于读取汽车的里程表:
class Car():
"""一次模拟汽车的简单尝试"""
def __init__(self, make, model, year):
"""初始化描述汽车的属性"""
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def get_descriptive_name(self):
"""返回整洁的描述性信息"""
long_name = str(self.year) + ' ' + self.make + ' ' + self.model
return long_name.title()
def read_odometer(self):
"""打印一条指出汽车里程的消息"""
print("This car has " + str(self.odometer_reading) + " miles on it.")
my_new_car = Car('audi', 'a4', 2016)
print(my_new_car.get_descriptive_name())
my_new_car.read_odometer()
现在,当Python调用方法__init__()来创建新实例时,将像前一个示例一样以属性的方式存储制造商、型号和生产年份。接下来,Python将创建一个名为odometer_reading的属性,并将其初始值设置为0。我们还定义了一个名为read_odometer()的方法,它让你能够轻松地获悉汽车的里程。
一开始汽车的里程为0。出售时里程表读数为0的汽车并不多,因此我们需要一个修改该属性的值的途径。
可以以三种不同的方式修改属性的值:直接通过实例进行修改;通过方法进行设置;通过方法进行递增(增加特定的值)。下面依次介绍这些方法。
要修改属性的值,最简单的方式是通过实例直接访问它。使用句点表示法来直接访问并设置汽车的属性odometer_reading
my_new_car.odometer_reading = 23
my_new_car.read_odometer()
如果有替你更新属性的方法,将大有裨益。这样,你就无需直接访问属性,而可将值传递给一个方法,由它在内部进行更新。
class Car():
"""一次模拟汽车的简单尝试"""
def __init__(self, make, model, year):
"""初始化描述汽车的属性"""
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def get_descriptive_name(self):
"""返回整洁的描述性信息"""
long_name = str(self.year) + ' ' + self.make + ' ' + self.model
return long_name.title()
def read_odometer(self):
"""打印一条指出汽车里程的消息"""
print("This car has " + str(self.odometer_reading) + " miles on it.")
def update_odometer(self, mileage):
"""将里程表读数设置为指定的值"""
self.odometer_reading = mileage
my_new_car.update_odometer(23)
my_new_car.read_odometer()
可对方法update_odometer()进行扩展,使其在修改里程表读数时做些额外的工作。下面来添加一些逻辑,禁止任何人将里程表读数往回调:
def update_odometer(self, mileage):
"""
将里程表读数设置为指定的值
禁止将里程表读数往回调
"""
if mileage >= self.odometer_reading:
self.odometer_reading = mileage
else:
print("You can't roll back an odometer!")
有时候需要将属性值递增特定的量,而不是将其设置为全新的值。假设我们购买了一辆二手车,且从购买到登记期间增加了100英里的里程,下面的方法让我们能够传递这个增量,并相应地增加里程表读数:
class Car():
"""一次模拟汽车的简单尝试"""
def __init__(self, make, model, year):
"""初始化描述汽车的属性"""
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def get_descriptive_name(self):
"""返回整洁的描述性信息"""
long_name = str(self.year) + ' ' + self.make + ' ' + self.model
return long_name.title()
def read_odometer(self):
"""打印一条指出汽车里程的消息"""
print("This car has " + str(self.odometer_reading) + " miles on it.")
def update_odometer(self, mileage):
"""将里程表读数设置为指定的值"""
self.odometer_reading = mileage
def increment_odometer(self, miles):
"""将里程表读数增加指定的量"""
self.odometer_reading += miles
my_new_car.update_odometer(23)
my_new_car.read_odometer()
my_used_car.update_odometer(23500)
my_used_car.read_odometer()
my_used_car.increment_odometer(100)
my_used_car.read_odometer()
你可以使用类似于上面的方法来控制用户修改属性值(如里程表读数)的方式,但能够访问程序的人都可以通过直接访问属性来将里程表修改为任何值。要确保安全,除了进行类似于前面的基本检查外,还需特别注意细节。
编写类时,并非总是要从空白开始。如果你要编写的类是另一个现成类的特殊版本,可使用继承。 **一个类继承另一个类时,它将自动获得另一个类的所有属性和方法;原有的类称为父类,而新的类称为子类。**子类继承了其父类的所有属性和方法,同时还可以定义自己的属性和方法。
**创建子类的实例时,Python首先需要完成的任务是给父类的所有属性赋值。**为此,子类的方法__init__()需要父类施以援手。
例如,下面来模拟电动汽车。电动汽车是一种特殊的汽车,因此我们可以在前面创建的Car类的基础上创建新类ElectricCar,这样我们就只需为电动汽车特有的属性和行为编写代码。
下面来创建一个简单的ElectricCar类版本,它具备Car类的所有功能:
class Car():
"""一次模拟汽车的简单尝试"""
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def get_descriptive_name(self):
long_name = str(self.year) + ' ' + self.make + ' ' + self.model
return long_name.title()
def read_odometer(self):
print("This car has " + str(self.odometer_reading) + " miles on it.")
def update_odometer(self, mileage):
if mileage >= self.odometer_reading:
self.odometer_reading = mileage
else:
print("You can't roll back an odometer!")
def increment_odometer(self, miles):
self.odometer_reading += miles
class ElectricCar(Car):
"""电动汽车的独特之处"""
def __init__(self, make, model, year):
super().__init__(make, model, year)
my_tesla = ElectricCar('tesla', 'model s', 2016)
my_tesla.get_descriptive_name()
创建子类时,父类必须包含在当前文件中,且位于子类前面。
定义子类时,必须在括号内指定父类的名称。方法__init__()接受创建父类Car实例所需的信息。
super()是一个特殊函数,帮助Python将父类和子类关联起来。这行代码让Python调用ElectricCar的父类的方法__init__(),让ElectricCar实例包含父类的所有属性。父类也称为超类(superclass),名称super()因此得名。
为测试继承是否能够正确地发挥作用,我们尝试创建一辆电动汽车,但提供的信息与创建普通汽车时相同。我们创建ElectricCar类的一个实例,并将其存储在变量my_tesla中。这行代码调用ElectricCar类中定义的方法__init__(),后者让Python调用父类Car中定义的方法__init__()。我们提供了实参’tesla’、'model s’和2016。
ElectricCar实例的行为与Car实例一样,现在可以开始定义电动汽车特有的属性和方法了。
让一个类继承另一个类后,可添加区分子类和父类所需的新属性和方法。
下面来添加一个电动汽车特有的属性(电瓶),以及一个描述该属性的方法。
class Car():
"""一次模拟汽车的简单尝试"""
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def get_descriptive_name(self):
long_name = str(self.year) + ' ' + self.make + ' ' + self.model
return long_name.title()
def read_odometer(self):
print("This car has " + str(self.odometer_reading) + " miles on it.")
def update_odometer(self, mileage):
if mileage >= self.odometer_reading:
self.odometer_reading = mileage
else:
print("You can't roll back an odometer!")
def increment_odometer(self, miles):
self.odometer_reading += miles
class ElectricCar(Car):
"""电动汽车的独特之处
初始化父类的属性,再初始化电动汽车独有的属性
"""
def __init__(self, make, model, year, battery=70):
super().__init__(make, model, year)
self.battery_size = battery
def describe_battery(self):
"""打印一条描述电瓶容量的消息"""
print("This car has " + str(self.battery_size) + "-kWh battery.")
my_tesla = ElectricCar('tesla', 'model s', 2016, 50)
my_tesla.get_descriptive_name()
my_tesla.describe_battery()
对于父类的方法,只要它不符合子类模拟的实物的行为,都可对其进行重写。为此,可在子类中定义一个这样的方法,即它与要重写的父类方法同名。这样,Python将不会考虑这个父类方法,而只关注你在子类中定义的相应方法。
假设Car类有一个名为fill_gas_tank()的方法,它对全电动汽车来说毫无意义,因此你可能想重写它。下面演示了一种重写方式:
class Car():
"""一次模拟汽车的简单尝试"""
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def get_descriptive_name(self):
long_name = str(self.year) + ' ' + self.make + ' ' + self.model
return long_name.title()
def read_odometer(self):
print("This car has " + str(self.odometer_reading) + " miles on it.")
def update_odometer(self, mileage):
if mileage >= self.odometer_reading:
self.odometer_reading = mileage
else:
print("You can't roll back an odometer!")
def increment_odometer(self, miles):
self.odometer_reading += miles
def fill_gas_tank(self):
print("This car has a gas tank.")
class ElectricCar(Car):
"""电动汽车的独特之处
初始化父类的属性,再初始化电动汽车独有的属性
"""
def __init__(self, make, model, year, battery=70):
super().__init__(make, model, year)
self.battery_size = battery
def describe_battery(self):
"""打印一条描述电瓶容量的消息"""
print("This car has " + str(self.battery_size) + "-kWh battery.")
def fill_gas_tank(self):
"""电动汽车没有油箱"""
print("This car doesn't need a gas tank!")
my_tesla = ElectricCar('tesla', 'model s', 2016, 50)
my_tesla.get_descriptive_name()
my_tesla.describe_battery()
my_tesla.fill_gas_tank()
现在,如果有人对电动汽车调用方法fill_gas_tank(),Python将忽略Car类中的方法fill_gas_tank(),转而运行上述代码。
使用代码模拟实物时,你可能会发现自己给类添加的细节越来越多,属性和方法清单以及文件都越来越长。在这种情况下,可能需要将类的一部分作为一个独立的类提取出来。你可以将大型类拆分成多个协同工作的小类。
例如,不断给ElectricCar类添加细节时,我们可能会发现其中包含很多专门针对汽车电瓶的属性和方法。在这种情况下,我们可将这些属性和方法提取出来,放到另一个名为Battery的类中,并将一个Battery实例用作ElectricCar类的一个属性:
class Car():
"""一次模拟汽车的简单尝试"""
def __init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def get_descriptive_name(self):
long_name = str(self.year) + ' ' + self.make + ' ' + self.model
return long_name.title()
def read_odometer(self):
print("This car has " + str(self.odometer_reading) + " miles on it.")
def update_odometer(self, mileage):
if mileage >= self.odometer_reading:
self.odometer_reading = mileage
else:
print("You can't roll back an odometer!")
def increment_odometer(self, miles):
self.odometer_reading += miles
def fill_gas_tank(self):
print("This car has a gas tank.")
class Battery():
"""一次模拟电动汽车电瓶的简单尝试"""
def __init__(self, battery_size=70):
self.battery_size = battery_size
def describe_battery(self):
"""打印一条描述电瓶容量的消息"""
print("This car has " + str(self.battery_size) + "-kWh battery.")
class ElectricCar(Car):
"""电动汽车的独特之处
初始化父类的属性,再初始化电动汽车独有的属性
"""
def __init__(self, make, model, year, battery=70):
super().__init__(make, model, year)
self.battery = Battery()
def fill_gas_tank(self):
"""电动汽车没有油箱"""
print("This car doesn't need a gas tank!")
my_tesla = ElectricCar('tesla', 'model s', 2016, 50)
my_tesla.get_descriptive_name()
my_tesla.battery.describe_battery()
我们定义了一个名为Battery的新类,它没有继承任何类。方法__init__()除self外,还有另一个形参battery_size。这个形参是可选的:如果没有给它提供值,电瓶容量将被设置为70。方法describe_battery()也移到了这个类中。
在ElectricCar类中,我们添加了一个名为self.battery的属性(见)。这行代码让Python创建一个新的Battery实例(由于没有指定尺寸,因此为默认值70),并将该实例存储在属性self.battery中。每当方法__init__()被调用时,都将执行该操作;因此现在每个ElectricCar实例都包含一个自动创建的Battery实例。
要描述电瓶时,让Python在实例my_tesla中查找属性battery,并对存储在该属性中的Battery实例调用方法describe_battery()。
这看似做了很多额外的工作,但现在我们想多详细地描述电瓶都可以,且不会导致ElectricCar类混乱不堪。下面再给Battery类添加一个方法,它根据电瓶容量报告汽车的续航里程
class Car():
--snip--
class Battery():
--snip--
def get_range(self):
"""打印一条消息,指出电瓶的续航里程"""
if self.battery_size == 70:
range = 240
elif self.battery_size == 85:
range = 270
message = "This car can go approximately " + str(range)
message += " miles on a full charge."
print(message)
class ElectricCar(Car):
--snip--
my_tesla = ElectricCar('tesla', 'model s', 2016)
print(my_tesla.get_descriptive_name())
my_tesla.battery.describe_battery()
my_tesla.battery.get_range()
注意:
在子类的__init__()中需要写实例化父类所需要的形参,因为子类会继承父类的所有属性,子类的__init__()中也会通过调用super()去调用父类的__init__()完成对父类中定义的属性的赋值。所以需要在子类的__init__()中写上形参,如下代码所示。
class Restaurant():
"""餐馆"""
def __init__(self, restaurant_name, cuisine_type):
self.restaurant_name = restaurant_name
self.cuisine_type = cuisine_type
self.number_served = 0
def describe_restaurant(self):
print("The restaurant's name is " + self.restaurant_name.title() + ".")
print("The restaurant serves " + self.cuisine_type.title() + " food.")
def open_restaurant(self):
print(self.restaurant_name + "is open.")
def set_number_served(self, number):
"""设置serve过的人数"""
self.number_served = number
def increment_number_served(self, increment):
self.number_served += increment
class IceCreamStand(Restaurant):
"""冰淇淋小店是一种特殊的餐馆。"""
def __init__(self, restaurant_name, cuisine_type):
super().__init__(restaurant_name, cuisine_type)
self.flavors = ['strawberry', 'vanilla', 'choco', 'watermelon', 'lemon', 'milk']
def describe_flavors(self):
for flavor in self.flavors:
print('The ' + flavor + ' flavor is served in ' + self.restaurant_name.title() + '.')
stand = IceCreamStand('sweet world', 'ice cream')
stand.describe_flavors()
模拟较复杂的物件(如电动汽车)时,需要解决一些有趣的问题。续航里程是电瓶的属性还是汽车的属性呢?
这让你进入了程序员的另一个境界:解决上述问题时,你从较高的逻辑层面(而不是语法层面)考虑;你考虑的不是Python,而是如何使用代码来表示实物。到达这种境界后,你经常会发现,**现实世界的建模方法并没有对错之分。有些方法的效率更高,但要找出效率最高的表示法,需要经过一定的实践。只要代码像你希望的那样运行,就说明你做得很好!**即便你发现自己不得不多次尝试使用不同的方法来重写类,也不必气馁;要编写出高效、准确的代码,都得经过这样的过程。
随着你不断地给类添加功能,文件可能变得很长,即便你妥善地使用了继承亦如此。为遵循Python的总体理念,应让文件尽可能整洁。可以将类存储在模块中,然后在主程序中导入所需的模块。
下面来创建一个只包含Car类的模块。
在第一行,我们包含了一个模块级文档字符串,对该模块的内容做了简要的描述。你应为自己创建的每个模块都编写文档字符串。
car.py:
"""一个可用于表示汽车的类"""
class Car():
"""一次模拟汽车的简单尝试"""
def __init__(self, make, model, year):
"""初始化描述汽车的属性"""
self.make = make
self.model = model
self.year = year
self.odometer_reading = 0
def get_descriptive_name(self):
"""返回整洁的描述性名称"""
long_name = str(self.year) + ' ' + self.make + ' ' + self.model
return long_name.title()
def read_odometer(self):
"""打印一条消息,指出汽车的里程"""
print("This car has " + str(self.odometer_reading) + " miles on it.")
def update_odometer(self, mileage):
"""
将里程表读数设置为指定的值
拒绝将里程表往回拨
"""
if mileage >= self.odometer_reading:
self.odometer_reading = mileage
else:
print("You can't roll back an odometer!")
def increment_odometer(self, miles):
"""将里程表读数增加指定的量"""
self.odometer_reading += miles
下面来创建另一个文件——my_car.py,在其中导入Car类并创建其实例:
my_car.py:
"""导入Car类并创建其实例"""
from car import Car
my_new_car = Car('audi', 'a4', 2016)
my_new_car.get_descriptive_name()
my_new_car.odometer_reading = 23
my_new_car.read_odometer()
import语句让Python打开模块car,并导入其中的Car类。这样我们就可以使用Car类了,就像它是在这个文件中定义的一样。
导入类是一种有效的编程方式。如果在这个程序中包含了整个Car类,它该有多长呀!通过将这个类移到一个模块中,并导入该模块,你依然可以使用其所有功能,但主程序文件变得整洁而易于阅读了。
同一个模块中的类之间应存在某种相关性,但可根据需要在一个模块中存储任意数量的类。
类Battery和ElectricCar都可帮助模拟汽车,因此下面将它们都加入模块car.py中:
car.py
"""一组用于表示燃油汽车和电动汽车的类"""
class Car():
--snip--
class Battery():
--snip--
class ElectricCar(Car):
--snip--
现在,可以新建一个名为my_electric_car.py的文件,导入ElectricCar类,并创建一辆电动汽车了:
my_electric_car.py
from car import ElectricCar
my_tesla = ElectricCar('tesla', 'model s', 2016)
print(my_tesla.get_descriptive_name())
my_tesla.battery.describe_battery()
my_tesla.battery.get_range()
可根据需要在程序文件中导入任意数量的类。从一个模块中导入多个类时,用逗号分隔了各个类。
my_cars.py
from car import Car, ElectricCar
my_beetle = Car('Volkswagen', 'beetle', 2016)
print(my_beetle.get_descriptive_name())
my_tesla = ElectricCar('tesla', 'roadster', 2016)
print(my_tesla.get_descriptive_name())
你还可以导入整个模块,再使用句点表示法访问需要的类。这种导入方法很简单,代码也易于阅读。由于创建类实例的代码都包含模块名,因此不会与当前文件使用的任何名称发生冲突。
my_cars.py
import car
my_beetle = car.Car('Volkswagen', 'beetle', 2016)
print(my_beetle.get_descriptive_name())
my_tesla = car.ElectricCar('tesla', 'roadster', 2016)
print(my_tesla.get_descriptive_name())
我们使用语法module_name.class_name访问需要的类。
要导入模块中的每个类,可使用下面的语法:
from modual_name import *
不推荐使用这种导入方式,其原因有二:
需要从一个模块中导入很多类时,最好导入整个模块,并使用module_name.class_name语法来访问类。
这样做时,虽然文件开头并没有列出用到的所有类,但你清楚地知道在程序的哪些地方使用了导入的模块;你还避免了导入模块中的每个类可能引发的名称冲突。
有时候,需要将类分散到多个模块中。你可能会发现一个模块中的类依赖于另一个模块中的类。在这种情况下,可在前一个模块中导入必要的类。
例如,下面将Car类存储在一个模块中,并将ElectricCar和Battery类存储在另一个模块中。我们将第二个模块命名为electric_car.py:
"""一组可用于表示电动汽车的类"""
from car import Car
class Battery():
--snip--
class ElectricCar(Car):
--snip--
现在可以分别从每个模块中导入类,以根据需要创建任何类型的汽车了:
my_cars.py
from car import Car
from electric_car import ElectricCar
my_beetle = Car('Volkswagen', 'beetle', 2016)
print(my_beetle.get_descriptive_name())
my_tesla = ElectricCar('tesla', 'roadster', 2016)
print(my_tesla.get_descriptive_name())
在组织大型项目的代码方面,Python提供了很多选项。熟悉所有这些选项很重要,这样你才能确定哪种项目组织方式是最佳的,并能理解别人开发的项目。
**一开始应让代码结构尽可能简单。**先尽可能在一个文件中完成所有的工作,确定一切都能正确运行后,再将类移到独立的模块中。如果你喜欢模块和文件的交互方式,可在项目开始时就尝试将类存储到模块中。先找出让你能够编写出可行代码的方式,再尝试让代码更为组织有序。
Python标准库是一组模块,安装的Python都包含它。
你现在对类的工作原理已有大致的了解,可以开始使用其他程序员编写好的模块了。可使用标准库中的任何函数和类,为此只需在程序开头包含一条简单的import语句。下面来看模块collections中的一个类——OrderedDict。
**字典让你能够将信息关联起来,但它们不记录你添加键—值对的顺序。**要创建字典并记录其中的键—值对的添加顺序,可使用模块collections中的OrderedDict类。OrderedDict实例的行为几乎与字典相同,区别只在于记录了键—值对的添加顺序。
from collections import OrderedDict
favorite_languages = OrderedDict()
favorite_languages['jen'] = 'python'
favorite_languages['sarah'] = 'c'
favorite_languages['edward'] = 'ruby'
favorite_languages['phil'] = 'python'
for name, language in favorite_languages.items():
print(name.title() + "'s favorite language is " +
language.title() + ".")
随着你对标准库的了解越来越深入,将熟悉大量可帮助你处理常见情形的模块。
你必须熟悉有些与类相关的编码风格问题,在你编写的程序较复杂时尤其如此。
类名应采用驼峰命名法,即将类名中的每个单词的首字母都大写,而不使用下划线。实例名和模块名都采用小写格式,并在单词之间加上下划线。
对于每个类,都应紧跟在类定义后面包含一个文档字符串。这种文档字符串简要地描述类的功能,并遵循编写函数的文档字符串时采用的格 式约定。每个模块也都应包含一个文档字符串,对其中的类可用于做什么进行描述。
可使用空行来组织代码,但不要滥用。在类中,可使用一个空行来分隔方法;而在模块中,可使用两个空行来分隔类。
需要同时导入标准库中的模块和你编写的模块时,先编写导入标准库模块的import语句,再添加一个空行,然后编写导入你自己编写的模块的import语句。在包含多条import语句的程序中,这种做法让人更容易明白程序使用的各个模块都来自何方。
学习处理文件和保存数据可让你的程序使用起来更容易:用户将能够选择输入什么样的数据,以及在什么时候输入;用户使用你的程序做一些工作后,可将程序关闭,以后再接着往下做。学习处理异常可帮助你应对文件不存在的情形,以及处理其他可能导致程序崩溃的问题。这让你的程序在面对错误的数据时更健壮——不管这些错误数据源自无意的错误,还是源自破坏程序的恶意企图。你在本章学习的技能可提高程序的适用性、可用性和稳定性。
要使用文本文件中的信息,首先需要将信息读取到内存中。
为此,你可以一次性读取文件的全部内容,也可以以每次一行的方式逐步读取。
read()
读取整个文件并直接
下面首先来创建一个文件,它包含精确到小数点后30位的圆周率值,且在小数点后每10位处都换行:
pi_digits.txt
3.1415926535
8979323846
2643383279
with open('pi_digits.txt') as file_object: # 创建了一个文件类的对象,因此能通过对象调用文件类中定义的方法
contents = file_object.read()
print(contents.rstrip())
在这个程序中,第1行代码做了大量的工作。
要以任何方式使用文件——哪怕仅仅是打印其内容,都得先打开文件,这样才能访问它。
函数open()接受一个参数:要打开的文件的名称。Python在当前执行的文件所在的目录中查找指定的文件。
关键字with在不再需要访问文件后将其关闭。 在这个程序中,注意到我们调用了open(),但没有调用close();你也可以调用open()和close()来打开和关闭文件,但这样做时,如果程序存在bug,导致close()语句未执行,文件将不会关闭。这看似微不足道,但未妥善地关闭文件可能会导致数据丢失或受损。如果在程序中过早地调用close(),你会发现需要使用文件时它已关闭(无法访问),这会导致更多的错误。并非在任何情况下都能轻松确定关闭文件的恰当时机,但通过使用前面所示的结构,可让Python去确定:你只管打开文件,并在需要时使用它,Python自会在合适的时候自动将其关闭。
如果不加rstrip()输出的末尾多了一个空行,因为read()到达文件末尾时返回一个空字符串,而将这个空字符串显示出来时就是一个空行。要删除多出来的空行,可在print语句中使用rstrip()
当你将类似pi_digits.txt这样的简单文件名传递给函数open()时,Python将在当前执行的文件(即.py程序文件)所在的目录中查找文件。可使用相对文件路径来打开该文件夹中的文件。
注意:在Windows系统中,在文件路径中使用反斜杠(\)而不是斜杠(/)
如:
with open('text_files\filename.txt') as file_object:
在相对路径行不通时,可使用绝对路径,即将文件在计算机中的准确位置。在Windows系统中,它们类似于下面这样:
file_path = 'C:\Users\ehmatthes\other_files\text_files\filename.txt'
with open(file_path) as file_object:
读取文件的一行时,注意每行的末尾都有一个看不见的换行符,而
直接对文件对象
file_object
进行遍历,可以以每次一行的方式检查文件
要以每次一行的方式检查文件,可对文件对象使用for循环:
file_name = 'pi_digits.txt'
with open(file_name) as file_object:
for line in file_object:
print(line)
通过对文件对象执行循环来遍历文件中的每一行。
我们打印每一行时,发现空白行更多了。为何会出现这些空白行呢?因为在这个文件中,每行的末尾都有一个看不见的换行符,而print语句也会加上一个换行符,因此每行末尾都有两个换行符:一个来自文件,另一个来自print语句。要消除这些多余的空白行,可在print语句中使用rstrip():
file_name = 'pi_digits.txt'
with open(file_name) as file_object:
for line in file_object:
print(line.rstrip())
方法readlines()从文件中读取每一行,并将其存储在一个列表中
file_name = 'pi_digits.txt'
with open(file_name) as file_object:
lines = file_object.readlines()
for line in lines:
print(line.rstrip())
将文件读取到内存中后,就可以以任何方式使用这些数据了。
file_name = 'pi_digits.txt'
with open(file_name) as file_object:
lines = file_object.readlines()
pi_string = ""
for line in lines:
pi_string += line.strip()
print(pi_string)
print(len(pi_string))
注意删除空格。
注意 读取文本文件时,Python将其中的所有文本都解读为字符串。 如果你读取的是数字,并要将其作为数值使用,就必须使用函数int()将其转换为整数,或使用函数float()将其转换为浮点数。
file_name = 'pi_million_digits.txt'
with open(file_name) as file_object:
lines = file_object.readlines()
pi_string = ""
for line in lines:
pi_string += line.strip()
print(pi_string[:52])
print(len(pi_string))
对于你可处理的数据量,Python没有任何限制;只要系统的内存足够多,你想处理多少数据都可以。
file_name = 'pi_million_digits.txt'
with open(file_name) as file_object:
lines = file_object.readlines()
pi_string = ''
for line in lines:
pi_string += line.strip()
mmddyy = input('please input your birthday:')
if mmddyy in pi_string:
print('Your birthday appears in the first million digits of pi!')
else:
print('Your birthday does not appear in the first million digits of pi!')
不包含哦
通过将输出写入文件,即便关闭包含程序输出的终端窗口,这些输出也依然存在。
要将文本写入文件,你在调用open()时需要提供另一个实参,告诉Python你要对打开的文件进行写入。
"""我们来将一条简单的消息存储到文件中,而不是将其打印到屏幕上"""
file_path = 'programming.txt'
with open(file_path, 'w') as file_object:
file_object.write("I love programmintg.")
调用open()时提供了两个实参(见Ø)。第一个实参也是要打开的文件的名称;
第二个实参(‘w’)告诉Python,我们要以写入模式打开这个文件。打开文件时,可指定读取模式(‘r’)、写入模式(‘w’)、附加模式(‘a’)或让你能够读取和写入文件的模式(‘r+’)。如果你省略了模式实参,Python将以默认的只读模式打开文件。
如果你要写入的文件不存在,函数open()将自动创建它。然而,以写入(‘w’)模式打开文件时千万要小心,因为如果指定的文件已经存在,Python将在返回文件对象前清空该文件。
注意 Python只能将字符串写入文本文件。要将数值数据存储到文本文件中,必须先使用函数str()将其转换为字符串格式。
函数write()不会在你写入的文本末尾添加换行符,因此要让每个语句的字符串都单独占一行,需要在write()语句中包含换行符:
file_path = 'programming.txt'
with open(file_path, 'w') as file_object:
file_object.write("I love programmintg.\n")
file_object.write("I love creating new games.\n")
像显示到终端的输出一样,还可以使用空格、制表符和空行来设置这些输出的格式。
如果你要给文件添加内容,而不是覆盖原有的内容,可以附加模式打开文件。你以附加模式打开文件时,Python不会在返回文件对象前清空文件,而你写入到文件的行都将添加到文件末尾。如果指定的文件不存在,Python将为你创建一个空文件。
file_path = 'programming.txt'
with open(file_path, 'w') as file_object:
file_object.write("I love programmintg.\n")
file_object.write("I love creating new games.\n")
with open(file_path, 'a') as file_object:
file_object.write("I also love finding meaning in large datasets.\n")
file_object.write("I love creating apps that can run in a browser.\n")
Python使用被称为异常 的特殊对象来管理程序执行期间发生的错误。每当发生让Python不知所措的错误时,它都会创建一个异常对象,比如下文的FileNotFoundError就是一个异常对象。如果你编写了处理该异常的代码,程序将继续运行。如果你未对异常进行处理,程序将停止,并显示一个traceback,其中包含有关异常的报告。
异常是使用try-except代码块处理的
下面来看一种导致Python引发异常的简单错误
print(5/0)
Traceback (most recent call last):
File "division.py", line 1, in <module>
print(5/0)
ZeroDivisionError: division by zero
在上述traceback中,最后一行指出的错误ZeroDivisionError是一个异常对象。Python无法按你的要求做时,就会创建这种对象。
当你认为可能发生了错误时,可编写一个try-except代码块来处理可能引发的异常。它会告诉Python如果这些代码引发了指定的异常,该怎么办。
try:
print(5/0)
except ZeroDivisionError:
print("You can't divide by zero!")
导致错误的代码被放在了一个try代码块中。如果try代码块中的代码运行起来没问题,Python将跳过excpt代码块。如果try代码块中的代码导致了错误,Python将查找该错误的except代码块,并运行其中的代码
发生错误时,如果程序还有工作没有完成,妥善地处理错误就尤其重要。这种情况经常会出现在要求用户提供输入的程序中;如果程序能够妥善地处理无效输入,就能再提示用户提供有效输入,而不至于崩溃。
程序崩溃可不好,但让用户看到traceback也不是好主意。不懂技术的用户会被它们搞糊涂,而且如果用户怀有恶意,他会通过traceback获悉你不希望他知道的信息。例如,他将知道你的程序文件的名称,还将看到部分不能正确运行的代码。有时候,训练有素的攻击者可根据这些信息判断出可对你的代码发起什么样的攻击。
"""一个只执行除法运算的简单计算器"""
print("Gimme two numbers, I'll divide them.")
print("Enter q to quit.")
while True:
num1 = input('\nFirst number:')
if num1 == 'q':
break
num2 = input('Second number:')
if num2 == 'q':
break
division = int(num1) / int(num2)
print(division)
通过将可能引发错误的代码放在try-except代码块中,可提高这个程序抵御错误的能力。错误是执行除法运算的代码行导致的,因此我们需要将它放到try-except代码块中。这个示例还包含一个else代码块;依赖于try代码块成功执行的代码都应放到else代码块中。
try代码块只包含可能导致错误的代码。依赖于try代码块成功执行的代码都放在else代码块中。except代码块告诉Python,出现ZeroDivisionError异常时该怎么办。
"""一个只执行除法运算的简单计算器"""
print("Gimme two numbers, I'll divide them.")
print("Enter q to quit.")
while True:
num1 = input('\nFirst number:')
if num1 == 'q':
break
num2 = input('Second number:')
if num2 == 'q':
break
try:
division = int(num1) / int(num2)
except ZeroDivisionError:
print("You can't divide by 0!")
else:
print(division)
使用文件时,一种常见的问题是找不到文件:你要查找的文件可能在其他地方、文件名可能不正确或者这个文件根本就不存在。对于所有这些情形,都可使用try-except代码块以直观的方式进行处理。
file_path = 'alice.txt'
try:
with open(file_path) as f_obj:
f_content = f_obj.read()
except FileNotFoundError:
print("The file " + file_path + " doesn't exist.")
f_path = 'alice.txt'
try:
with open(f_path, encoding='utf-8') as f_obj:
f_content = f_obj.read()
except FileNotFoundError:
msg = 'The file ' + f_path + ' does not exist.'
print(msg)
else:
words = f_content.split()
num_words = len(words)
print("The file has about " + str(num_words) + " words.")
下面多分析几本书。我们将这个程序的大部分代码移到一个名为count_words()的函数中,这样对多本书进行分析时将更容易。
修改程序的同时更新注释是个不错的习惯,因此我们将注释改成了文档字符串。
"""下面多分析几本书"""
def count_words(filename):
"""计算一个文件大致包含多少个单词"""
try:
with open(filename, encoding='utf-8') as f_obj:
f_content = f_obj.read()
except FileNotFoundError:
print("The file " + filename + " doesn't exist.")
else:
words_list = f_content.split()
count = len(words_list)
print("The file has about " + str(count) + " words.")
book_list = ['alice.txt', 'Siddhartha.txt', 'moby_dick.txt', 'little_women.txt']
for book in book_list:
count_words(book)
并非每次捕获到异常时都需要告诉用户,有时候你希望程序在发生异常时一声不吭,就像什么都没有发生一样继续运行。要让程序在失败时一声不吭,可像通常那样编写try代码块,但在except代码块中明确地告诉Python什么都不要做。Python有一个pass语句,可在代码块中使用它来让Python什么都不要做。
pass语句还充当了占位符,它提醒你在程序的某个地方什么都没有做,并且以后也许要在这里做些什么。
"""下面多分析几本书"""
def count_words(filename):
"""计算一个文件大致包含多少个单词"""
try:
with open(filename, encoding='utf-8') as f_obj:
f_content = f_obj.read()
except FileNotFoundError:
pass
else:
words_list = f_content.split()
count = len(words_list)
print("The file has about " + str(count) + " words.")
book_list = ['alice.txt', 'Siddhartha.txt', 'moby_dick.txt', 'little_women.txt']
for book in book_list:
count_words(book)
在什么情况下该向用户报告错误?在什么情况下又应该在失败时一声不吭呢?如果用户知道要分析哪些文件,他们可能希望在有文件没有分析时出现一条消息,将其中的原因告诉他们。如果用户只想看到结果,而并不知道要分析哪些文件,可能就无需在有些文件不存在时告知他们。向用户显示他不想看到的信息可能会降低程序的可用性。Python的错误处理结构让你能够细致地控制与用户分享错误信息的程度,要分享多少信息由你决定。
编写得很好且经过详尽测试的代码不容易出现内部错误,如语法或逻辑错误,但只要程序依赖于外部因素,如用户输入、存在指定的文件、有网络链接,就有可能出现异常。凭借经验可判断该在程序的什么地方包含异常处理块,以及出现错误时该向用户提供多少相关的信息。
很多程序都要求用户输入某种信息,如让用户存储游戏首选项或提供要可视化的数据。不管专注的是什么,程序都把用户提供的信息存储在列表和字典等数据结构中,用户关闭程序时,你几乎总是要保存他们提供的信息。一种简单的方式是使用模块json来储存数据。
模块json让你能够将简单的Python数据结构转储到文件中,并在程序再次运行时加载该文件中的数据。你还可以使用json在Python程序之间分享数据。更重要的是,JSON数据格式并非Python专用的,这让你能够将以JSON格式存储的数据与使用其他编程语言的人分享。这是一种轻便格式,很有用,也易于学习。
JSON(Javascript Object Notation)格式最初是为Javascript开发的,但随后成为了一种常见格式,被包括Python在内的众多语言采用。
首先编写一个存储一组数字的简短程序,再把数据读取到内存中。
json.dump()
将数据存储到json文件中。接受两个实参,第一个是要存储的数据,第二个是文件对象。json.dump()
函数把数据存储到文件对象中。import json
numbers = [2, 3, 5, 7, 11, 13]
filename = 'numbers.json'
with open(filename, 'w') as f_object:
json.dump(numbers, f_object) # 将数据存入文件对象
这个程序没有输出,但我们可以打开文件numbers.json,看看其内容。数据的存储格式与Python中一样。
使用json.load()将json文件中的这个列表读取到内存中:
import json
filename = 'numbers.json'
with open(filename) as f_object: # 读取json文件时,json文件也需要先打开
numbers = json.load(f_object)
print(numbers)
这是一种在程序之间共享数据的简单方式。
对于用户生成的数据,使用json保存它们大有裨益,否则等程序停止运行时用户的信息将丢失。
下面的代码在用户首次运行程序时输入自己的名字,将用户的名字保存在json文件中,这样再次运行程序时就记住他了。
remember_me.py
"""存储用户的名字"""
import json
filename = 'username.json'
username = input("What's your name please?")
with open(filename, 'w') as f_obj:
json.dump(username, f_obj)
print("We will remember you when you come back, " + username + " !")
现在再编写一个程序,向名字被存储的用户发出问候:
greet_user.py
"""向其名字被存储的用户发出问候"""
import json
filename = 'username.json'
with open(filename) as f_obj:
username = json.load(f_obj)
print("Hello! Welcome come back, " + username + " !")
现在将这两个程序合并到一个程序(remember_me.py)中。在这个程序中,我们将尝试从文件username.json中获取用户名,因此我们首先编写一个尝试恢复用户名的try代码块。如果这个文件不存在,我们就在except代码块中提示用户输入用户名,并将其存储在username.json中,以便程序再次运行时能够获取它:
remember_me.py
import json
file_name = 'username.json'
try:
with open(file_name) as f_obj:
username = json.load(f_obj)
except FileExistsError:
username = input("User not exsist. Please input your name:")
with open(file_name, 'w') as f_obj:
json.dump(username, f_obj)
print("We'll remember you, ", username, "!")
else:
print("Hello, welcome back, ", username, " !")
我们先尝试打开文件username.json。如果这个文件存在,就将其中的用户名读取到内存中,再执行else代码块。用户首次运行这个程序时,文件username.json不存在,将引发FileNotFoundError异常,此时Python将执行except代码块。
无论执行的是except代码块还是else代码块,都将显示用户名和合适的问候语。
将代码划分为一系列完成具体工作的函数,这样的过程被称为重构。
重构让代码更清晰、更易于理解、更容易扩展。
接下来我们重构remember_me.py。
remember_me.py的重点是问候用户,因此我们将其所有代码都放到一个名为greet_user()的函数中:
remember_me.py
"""重构代码,将其所有代码都放到一个名为greet_user()的函数中"""
import json
def greet_user():
"""问候用户,并指出其名字"""
file_name = 'username.json'
try:
with open(file_name) as f_obj:
username = json.load(f_obj)
except FileExistsError:
username = input("User not exsist. Please input your name:")
with open(file_name, 'w') as f_obj:
json.dump(username, f_obj)
print("We'll remember you, ", username, "!")
else:
print("Hello, welcome back, ", username, " !")
这个程序更清晰些,但函数greet_user()所做的不仅仅是问候用户,还在存储了用户名时获取它,而在没有存储用户名时提示用户输入一个。
下面来重构greet_user(),让它不执行这么多任务。
首先将获取存储的用户名的代码移到另一个函数中:
remember_me.py
"""重构代码,让greet_user()不执行这么多任务"""
import json
def get_stored_username(file_name):
"""如果存储了用户名,就获取它"""
file_name = 'username.json'
try:
with open(file_name) as f_obj:
username = json.load(file_name)
except FileExistsError:
return None
else:
return username
# print("Hello, welcome back, ", username, "!")
def greet_user():
"""问候用户,并指出其名字"""
username = get_stored_username()
if username:
print("Hello, welcome back, ", username, "!")
else:
username = input("Please input your name:")
file_name = 'username.json'
with open(file_name, 'w') as f_obj:
json.dump(username, f_obj)
print("Hello {}, We'll remember you next time.".format(username))
如果文件username.json不存在,get_stored_username()函数就返回None。这是一种不错的做法:函数要么返回预期的值,要么返回None;这让我们能够使用函数的返回值做简单测试。
我们还需将greet_user()中的另一个代码块提取出来:没有存储用户名的json文件时提示用户输入用户名,并存储到json文件中:
remember_me.py
"""重构代码。将没有存储用户名时提示用户输入的代码放在一个独立的函数中"""
import json
def get_stored_username(file_name):
"""如果存储了用户名,就获取它"""
file_name = 'username.json'
try:
with open(file_name) as f_obj:
username = json.load(file_name)
except FileExistsError:
return None
else:
return username
# print("Hello, welcome back, ", username, "!")
def get_new_username():
"""对于新用户,提示用户输入用户名,并存储用户名,不用欢迎"""
username = input("Please input your name:")
file_name = 'username.json'
with open(file_name, 'w') as f_obj:
json.dump(username, f_obj)
# print("Hello {}, We'll remember you next time.".format(username))
def greet_user():
"""问候用户,并指出其名字"""
username = get_stored_username()
if username:
print("Hello, welcome back, ", username, "!")
else:
get_new_username()
print("Hello {}, We'll remember you next time.".format(username))
remember_me.py的这个最终版本,每个函数都执行单一而清晰的任务。
我们调用greet_user(),它打印一条合适的消息:要么欢迎老用户回来,要么问候新用户。为此,它首先调用get_stored_username(),这个函数只负责获取存储的用户名(如果存储了的话),再在必要时调用get_new_username(),这个函数只负责获取并存储新用户的用户名。
版本控制软件让你能够拍摄处于可行状态的项目的快照。修改项目(如实现新功能)后,如果项目不能正常运行,可恢复到前一个可行状态。
sudo apt-get install git
可以先输入git查看是否已经安装了
要在Windows系统中安装Git,请访问http://msysgit.github.io/,并单击Download。
Git跟踪谁修改了项目,哪怕参与项目开发的人只有一个。为此,Git需要知道你的用户名和电子邮件地址
git config --global user.name "Crescent"
git config --global user.email "[email protected]"
如果你忘记了这一步,在你首次提交时,Git将提示你提供这些信息。
创建一个要进行版本控制的项目。
在你的系统中创建一个文件夹,并将其命名为git_practice。在这个文件夹中,创建一个简单的Python程序:
hello_world.py
print("Hello Git world!")
我们将使用这个程序来探索Git的基本功能。
扩展名为.pyc的文件是根据.py文件自动生成的,因此我们无需让Git跟踪它们。这些文件存储在目录__pycache__中。为让Git忽略这个目录,创建一个名为.gitignore的特殊文件(这个文件名以句点打头,且没有扩展名),并在其中添加下面一行内容
.gitignore
__pycache__/
这让Git忽略目录__pycache__中的所有文件。使用文件.gitignore可避免项目混乱,开发起来更容易。
git init
你创建了一个目录,其中包含一个Python文件和一个.gitignore文件,可以初始化一个Git仓库了。为此,打开一个终端窗口,切换到文件夹git_practice,并执行如下命令:
git_practice$ git init
Initialized empty Git repository in git_practice/.git/
git_practice$
输出表明Git在git_practice中初始化了一个空仓库。仓库是程序中被Git主动跟踪的一组文件。Git用来管理仓库的文件都存储在隐藏的.git/中,你根本不需要与这个目录打交道,但千万不要删除这个目录,否则将丢弃项目的所有历史记录。
执行其他操作前,先来看一下项目的状态:
git_practice$ git status
# On branch master
#
# Initial commit
#
# Untracked files:
# (use "git add ..." to include in what will be committed)
#
# .gitignore
# hello_world.py
#
nothing added to commit but untracked files present (use "git add" to track)
git_practice$
在Git中,分支是项目的一个版本。从这里的输出可知,我们位于分支master上。每次查看项目的状态时,输出都将指出你位于分支master上。接下来的输出表明,我们将进行初始提交。提交是项目在特定时间点的快照。
git_practice$ git add .
git_practice$ git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
# (use "git rm --cached ..." to unstage)
#
# new file: .gitignore
# new file: hello_world.py
#
git_practice$
命令git add .将项目中未被跟踪的所有文件都加入到仓库中。它不提交这些文件,而只是让Git开始关注它们。
git_practice$ git commit -m "Started project."
[master (root-commit) c03d2a3] Started project.
2 files changed, 1 insertion(+)
create mode 100644 .gitignore
create mode 100644 hello_world.py
git_practice$ git status
# On branch master
nothing to commit, working directory clean
git_practice$
执行命令git commit -m "message"
以拍摄项目的快照。标志-m
让Git将接下来的消息(“Started project.”)记录到项目的历史记录中。
git_practice$ git log
commit a9d74d87f1aa3b8f5b2688cb586eac1a908cfc7f
Author: Eric Matthes <[email protected]>
Date: Mon Mar 16 07:23:32 2015 -0800
Started project.
git_practice$
每次提交时,Git都会生成一个包含40字符的独一无二的引用ID。它记录提交是谁执行的、提交的时间以及提交时指定的消息。
git_practice$ git log --pretty=oneline
a9d74d87f1aa3b8f5b2688cb586eac1a908cfc7f Started project.
git_practice$
标志--pretty=oneline
指定显示两项最重要的信息:提交的引用ID以及为提交记录的消息。
现在,对项目进行修改,并提交所做的修改。为此,我们在
hello_world.py中再添加一行代码:
hello_world.py
print("Hello Git world!")
print("Hello everyone.")
查看项目的状态,Git注意到了这个文件发生了变化:
git_practice$ git status
# On branch master
# Changes not staged for commit:
# (use "git add ..." to update what will be committed)
# (use "git checkout -- ..." to discard changes in working directory)
#
# modified: hello_world.py
#
no changes added to commit (use "git add" and/or "git commit -a")
git_practice$
输出指出了我们当前所在的分支、被修改了的文件的名称,还指出了所做的修改未提交。
提交所做的修改,并再次查看状态:
git_practice$ git commit -am "Extended greeting."
[master 08d4d5e] Extended greeting.
1 file changed, 1 insertion(+)
git_practice$ git status
# On branch master
nothing to commit, working directory clean
git_practice$ git log --pretty=oneline
08d4d5e39cb906f6cff197bd48e9ab32203d7ed6 Extended greeting.
be017b7f06d390261dbc64ff593be6803fd2e3a1 Started project.
git_practice$
标志-a
让Git将仓库中所有修改了的文件都加入到当前提交中(如果你在两次提交之间创建了新文件,可再次执行命令git add .
将这些新文件加入到仓库中)。标志-m
让Git在提交历史中记录一条消息。
-a
只能将修改过的文件进行add
、commit
一步到位,但是创建的新文件不可以,需要执行git add .
如何放弃所做的修改,恢复到前一个可行状态?
首先在hello_world.py中再添加一行代码:
hello_world.py
print("Hello Git world!")
print("Hello everyone.")
print("Oh no, I broke the project!")
保存并运行这个文件。
查看状态,Git注意到了所做的修改:
git_practice$ git status
# On branch master
# Changes not staged for commit:
# (use "git add ..." to update what will be committed)
# (use "git checkout -- ..." to discard changes in working directory)
#
# modified: hello_world.py
#
no changes added to commit (use "git add" and/or "git commit -a")
git_practice$
这次我们不提交所做的修改,而要恢复到最后一个提交(我们知道,那次提交时项目能够正常地运行)
git_practice$ git checkout .
git_practice$ git status
# On branch master
nothing to commit, working directory clean
git_practice$
命令git checkout
让你能够恢复到以前的任何提交。命令git checkout .
放弃自最后一次提交后所做的所有修改
,将项目恢复到最后一次提交的状态。
你可以检出提交历史中的任何提交,而不仅仅是最后一次提交,为此可在命令git check
末尾指定该提交的引用ID的前6个字符(而不是句点)。
git_practice$ git log --pretty=oneline
08d4d5e39cb906f6cff197bd48e9ab32203d7ed6 Extended greeting.
be017b7f06d390261dbc64ff593be6803fd2e3a1 Started project.
git_practice$ git checkout be017b
Note: checking out 'be017b'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:
git checkout -b new_branch_name
HEAD is now at be017b7... Started project.
git_practice$
检出以前的提交后,你将离开分支master,并进入Git所说的分离头指针(detached HEAD)状态。HEAD表示项目的当前状态,之所以说我们处于分离状态,是因为我们离开了一个命名分支(这里是master)。
要回到分支master,可检出它:
git_practice$ git checkout master
Previous HEAD position was be017b7... Started project.
Switched to branch 'master'
git_practice$
除非你要使用Git的高级功能,否则在检出以前的提交后,最好不要对项目做任何修改。
如果想放弃较近的所有提交,并恢复到以前的状态,也可以将项目重置到以前的提交。为此,可在处于分支master上的情况下,执行如下命令:
git_practice$ git status
# On branch master
nothing to commit, working directory clean
git_practice$ git log --pretty=oneline
08d4d5e39cb906f6cff197bd48e9ab32203d7ed6 Extended greeting.
be017b7f06d390261dbc64ff593be6803fd2e3a1 Started project.
git_practice$ git reset --hard be017b
HEAD is now at be017b7 Started project.
git_practice$ git status
# On branch master
nothing to commit, working directory clean
git_practice$ git log --pretty=oneline
be017b7f06d390261dbc64ff593be6803fd2e3a1 Started project.
git_practice$
有时候,仓库的历史记录被你搞乱了,而你又不知道如何恢复。在这种情况下,你首先应考虑使用附录C介绍的方法寻求帮助。如果无法恢复且参与项目开发的只有你一个人,可继续使用这些文件,但要将项目的历史记录删除——删除目录.git
。这不会影响任何文件的当前状态,而只会删除所有的提交,因此你将无法检出项目的其他任何状态。
可以打开一个文件浏览器,并将目录.git删除,也可通过命令行完成这个任务:
git_practice$ git status
# On branch master
nothing to commit, working directory clean
git_practice$ rm -rf .git
git_practice$ git status
fatal: Not a git repository (or any of the parent directories): .git
git_practice$ git init
Initialized empty Git repository in git_practice/.git/
git_practice$ git status
# On branch master
#
# Initial commit
#
# Untracked files:
# (use "git add ..." to include in what will be committed)
#
# .gitignore
# hello_world.py
#
nothing added to commit but untracked files present (use "git add" to track)
git_practice$ git add .
git_practice$ git commit -m "Starting over."
[master (root-commit) 05f5e01] Starting over.
2 files changed, 2 insertions(+)
create mode 100644 .gitignore
create mode 100644 hello_world.py
git_practice$ git status
# On branch master
nothing to commit, working directory clean
git_practice$
需要经过一定的练习才能学会使用版本控制,但一旦开始使用,你就再也离不开它。