目录
一、数据结构
列表
列表是一种用于保存一系列有序项目的集合。项目的列表应该用方括号括起来,这样 Python 才能理解到你正在指定一张列表。一旦你创建了一张列表,你可以添加、移除或搜索列表中的项目。
shoplist = ['apple', 'mango', 'carrot', 'banana']
print('I have', len(shoplist), 'items to purchase.')
print('These items are:', end=' ')
for item in shoplist:
print(item, end=' ')
print('\nI also have to buy rice.')
shoplist.append('rice')
print('My shopping list is now', shoplist)
print('I will sort my list now')
shoplist.sort()
print('Sorted shopping list is', shoplist)
print('The first item I will buy is', shoplist[0])
olditem = shoplist[0]
del shoplist[0]
print('I bought the', olditem)
print('My shopping list is now', shoplist)
#输出
#I have 4 items to purchase.
#These items are: apple mango carrot banana
#I also have to buy rice.
#My shopping list is now ['apple', 'mango', 'carrot', 'banana', 'rice']
#I will sort my list now
#Sorted shopping list is ['apple', 'banana', 'carrot', 'mango', 'rice']
#The first item I will buy is apple
#I bought the apple
#My shopping list is now ['banana', 'carrot', 'mango', 'rice']
- 列表是可变的(Mutable)而字符串是不可变的(Immutable)。
- 我们希望移除列表中的第一个商品,因此我们使用del shoplise[0] (要记住 Python 从 0 开始计数)。
元组
- 元组(Tuple)用于将多个对象保存到一起。你可以将它们近似地看作列表,但是元组不能提供列表类能够提供给你的广泛的功能。元组的一大特征类似于字符串,它们是不可变的,也就是说,你不能编辑或更改元组。
- 元组是通过特别指定项目来定义的,在指定项目时,你可以给它们加上括号,并在括号内部用逗号进行分隔。
- 元组通常用于保证某一语句或某一用户定义的函数可以安全地采用一组数值,意即元组内的数值不会改变。
zoo = ('python', 'elephant', 'penguin')
print('Number of animals in the zoo is', len(zoo))
new_zoo = 'monkey', 'camel', zoo
print('Number of cages in the new zoo is', len(new_zoo))
print('All animals in new zoo are', new_zoo)
print('Animals brought from old zoo are', new_zoo[2])
print('Last animal brought from old zoo is', new_zoo[2][2])
print('Number of animals in the new zoo is',len(new_zoo)-1+len(new_zoo[2]))
#输出
#Number of animals in the zoo is 3
#Number of cages in the new zoo is 3
#All animals in new zoo are ('monkey', 'camel', ('python', 'elephant', 'penguin'))
#Animals brought from old zoo are ('python', 'elephant', 'penguin')
#Last animal brought from old zoo is penguin
#Number of animals in the new zoo is 5
- 变量 zoo 指的是一个包含项目的元组。我们能够看到 len 函数在此处用来获取元组的长度。这也表明元组同时也是一个序列。
- 我们可以通过在方括号中指定项目所处的位置来访问元组中
的各个项目。这种使用方括号的形式被称作索引(Indexing)运算符。我们通过指定new_zoo [2] 来指定 new_zoo 中的第三个项目,们也可以通过指定 new_zoo[2][2] 来指定new_zoo 元组中的第三个项目中的第三个项目 。
字典
字典将键值(Keys)(即姓名)与值(Values)(即地
址等详细信息)联立到一起,在这里要注意到键值必须是唯一的。
- 只能使用不可变的对象(如字符串)作为字典的键值,但是你可以使用可变或不可变的对象作为字典中的值。
- 在字典中,可以通过使用符号构成 d = {key : value1 , key2 : value2} 这样的形式,来成对地指定键值与值。在这里要注意到成对的键值与值之间使用冒号分隔,而每一对键值与值则使用逗号进行区分,它们全都由一对花括号括起。
- 字典中的成对的键值—值配对不会以任何方式进行排序。如果你希望为它们安排一个特别的次序,只能在使用它们之前自行进行排序。
ab = {
'Swaroop': 'swaroop@swaroopch.com',
'Larry': 'larry@wall.org',
'Matsumoto': 'matz@ruby-lang.org',
'Spammer': 'spammer@hotmail.com'
}
print("Swaroop's address is", ab['Swaroop'])
# 删除一对键值—值配对
del ab['Spammer']
print('\nThere are {} contacts in the address-book\n'.format(len(ab)))
for name, address in ab.items():
print('Contact {} at {}'.format(name, address))
# 添加一对键值—值配对
ab['Guido'] = 'guido@python.org'
if 'Guido' in ab:
print("\nGuido's address is", ab['Guido'])
#输出
#Swaroop's address is swaroop@swaroopch.com
#There are 3 contacts in the address-book
#Contact Swaroop at swaroop@swaroopch.com
#Contact Matsumoto at matz@ruby-lang.org
#Contact Larry at larry@wall.org
#Guido's address is guido@python.org
序列
列表、元组和字符串可以看作序列(Sequence)的某种表现形式。上面所提到的序列的三种形态——列表、元组与字符串,同样拥有一种切片(Slicing)运算符,它能够允许我们序列中的某段切片——也就是序列之中的一部分。
shoplist = ['apple', 'mango', 'carrot', 'banana']
name = 'swaroop'
# Indexing or 'Subscription' operation #
# 索引或“下标(Subscription)”操作符 #
print('Item 0 is', shoplist[0])
print('Item 1 is', shoplist[1])
print('Item 2 is', shoplist[2])
print('Item 3 is', shoplist[3])
print('Item -1 is', shoplist[-1])
print('Item -2 is', shoplist[-2])
print('Character 0 is', name[0])
# Slicing on a list #
print('Item 1 to 3 is', shoplist[1:3])
print('Item 2 to end is', shoplist[2:])
print('Item 1 to -1 is', shoplist[1:-1])
print('Item start to end is', shoplist[:])
# 从某一字符串中切片 #
print('characters 1 to 3 is', name[1:3])
print('characters 2 to end is', name[2:])
print('characters 1 to -1 is', name[1:-1])
print('characters start to end is', name[:])
#输出
#Item 0 is apple
#Item 1 is mango
#Item 2 is carrot
#Item 3 is banana
#Item -1 is banana
#Item -2 is carrot
#Character 0 is s
#Item 1 to 3 is ['mango', 'carrot']
#Item 2 to end is ['carrot', 'banana']
#Item 1 to -1 is ['mango', 'carrot']
#Item start to end is ['apple', 'mango', 'carrot', 'banana']
#characters 1 to 3 is wa
#characters 2 to end is aroop
#characters 1 to -1 is waroo
#characters start to end is swaroop
- 索引操作也可以使用负数,在这种情况下,位置计数将从队列的末尾开始。因此, shoplist[-1] 指的是序列的最后一个项目, shoplist[-2] 将获取序列中倒数第二个项目。
- 在切片操作中,第一个数字(冒号前面的那位)指的是切片开始的位置,第二个数字(冒号后面的那位)指的是切片结束的位置。如果第一位数字没有指定,Python 将会从序列的起始处开始操作。如果第二个数字留空,Python 将会在序列的末尾结束操作。要注意的是切片操作会在开始处返回 start,并在 end 前面的位置结束工作。也就是说,序列切片将包括起始位置,但不包括结束位置。
- 同样可以在切片操作中使用负数位置。使用负数时位置将从序列末端开始计算。例如, shoplist[:-1] 强返回一组序列切片,其中不包括序列的最后一项项目,但其它所有项目都包含其中。
- 同样可以在切片操作中提供第三个参数,这一参数将被视为切片的步长(Step)(在默认情况下,步长大小为 1):
>>> shoplist = ['apple', 'mango', 'carrot', 'banana']
>>> shoplist[::1]
['apple', 'mango', 'carrot', 'banana']
>>> shoplist[::2]
['apple', 'carrot']
>>> shoplist[::3]
['apple', 'banana']
>>> shoplist[::-1]
['banana', 'carrot', 'mango', 'apple']
集合
集合(Set)是简单对象的无序集合(Collection)。当集合中的项目存在与否比起次序或其出现次数更加重要时,我们就会使用集合。
通过使用集合,可以测试某些对象的资格或情况,检查它们是否是其它集合的子集,找到两个集合的交集。
>> bri = set(['brazil', 'russia', 'india'])
>>> 'india' in bri
True
>>> 'usa' in bri
False
>>> bric = bri.copy()
>>> bric.add('china')
>>> bric.issuperset(bri)
True
>>> bri.remove('russia')
>>> bri & bric # OR bri.intersection(bric)
{'brazil', 'india'}
当你创建了一个对象并将其分配给某个变量时,变量只会查阅(Refer)某个对象,并且它也不会代表对象本身。也就是说,变量名只是指向你计算机内存中存储了相应对象的那一部分。这叫作将名称绑定(Binding)给那一个对象。
print('Simple Assignment')
shoplist = ['apple', 'mango', 'carrot', 'banana']
# mylist 只是指向同一对象的另一种名称
mylist = shoplist
# 我购买了第一项项目,所以我将其从列表中删除
del shoplist[0]
print('shoplist is', shoplist)
print('mylist is', mylist)
# 注意到 shoplist 和 mylist 二者都
# 打印出了其中都没有 apple 的同样的列表,以此我们确认
# 它们指向的是同一个对象
print('Copy by making a full slice')
# 通过生成一份完整的切片制作一份列表的副本
mylist = shoplist[:]
# 删除第一个项目
del mylist[0]
print('shoplist is', shoplist)
print('mylist is', mylist)
# 注意到现在两份列表已出现不同
#输出
#Simple Assignment
#shoplist is ['mango', 'carrot', 'banana']
#mylist is ['mango', 'carrot', 'banana']
#Copy by making a full slice
#shoplist is ['mango', 'carrot', 'banana']
#mylist is ['carrot', 'banana']
两个list出现不同的主要原因是,前一个操作是句柄拷贝,指向的是同一个对象;后一个操作是对象拷贝,所以进行del操作时没有影响原始数据。
字符串的方法
# 这是一个字符串对象
name = 'Swaroop'
if name.startswith('Swa'):
print('Yes, the string starts with "Swa"')
if 'a' in name:
print('Yes, it contains the string "a"')
if name.find('war') != -1:
print('Yes, it contains the string "war"')
delimiter = '_*_'
mylist = ['Brazil', 'Russia', 'India', 'China']
print(delimiter.join(mylist))
#输出
#Yes, the string starts with "Swa"
#Yes, it contains the string "a"
#Yes, it contains the string "war"
#Brazil_*_Russia_*_India_*_China
- startwith 方法用于查找字符串是否以给定的字符串内容开头。
- in 运算符用以检查给定的字符串是否是查询的字符串中的一
部分。 - find 方法用于定位字符串中给定的子字符串的位置。如果找不到相应的子字符串, find会返回 -1。
- str 类同样还拥有一个简洁的方法用以 联结(Join) 序列中的项目,其中字符串将会作为每一项目之间的分隔符,并以此生成并返回一串更大的字符串。
二、OOP
self
类方法与普通函数只有一种特定的区别——前者必须有一个额外的名字,这个名字必须添加到参数列表的开头,但是你不用在你调用这个功能时为这个参数赋值,Python 会为它提供。这种特定的变量引用的是对象本身,按照惯例,它被赋予 self 这一名称。
(Python 中的 self 相当于 C++ 中的指针以及 Java 与 C# 中的 this 指针。)
类
最简单的类(Class)如下:
class Person:
pass # 一个空的代码块
p = Person()
print(p)
- 通过使用 class 语句与这个类的名称来创建一个新类。在它之后是一个缩进的语句块,代表这个类的主体。在本案例中,我们创建的是一个空代码块,使用 pass 语句予以标明。
- 然后,我们通过采用类的名称后跟一对括号的方法,给这个类创建一个对象。
方法
class Person:
def say_hi(self):
print('Hello, how are you?')
p = Person()
p.say_hi()
# 前面两行同样可以写作
# Person().say_hi()
#输出
#Hello, how are you?
say_hi 这一方法不需要参数,但是依旧在函数定义中拥有 self 变量。
_init_方法
init 方法会在类的对象被实例化(Instantiated)时立即运行。这一方法可以对任何你想进行操作的目标对象进行初始(Initialization)操作。这里你要注意在 init 前后加上的双下划线。
class Person:
def __init__(self, name):
self.name = name
def say_hi(self):
print('Hello, my name is', self.name)
p = Person('Swaroop')
p.say_hi()
# 前面两行同时也能写作
# Person('Swaroop').say_hi()
#输出
#Hello, my name is Swaroop
- 在本例中,我们定义 init 方法用以接受 name 参数(与更普遍的 self 一道)。在这里,我们创建了一个字段,同样称为 name 。要注意到尽管它们的名字都是“name”,但这是两个不相同的变量。
- self.name 意味着这个叫作“name”的东西是某个叫作“self”的对象的一部分,而另一个 name 则是一个局部变量。
- 当我们在 Person 类下创建新的实例 p 时,我们采用的方法是先写下类的名称,后跟括在括号中的参数,形如: p = Person(‘Swaroop’) 。我们不会显式地调用 init 方法。
类变量与对象变量
- 类变量(Class Variable)是共享的(Shared)——它们可以被属于该类的所有实例访问。该类变量只拥有一个副本,当任何一个对象对类变量作出改变时,发生的变动将在其它所有实例中都会得到体现。
- 对象变量(Object variable)由类的每一个独立的对象或实例所拥有。在这种情况下,每个对象都拥有属于它自己的字段的副本,也就是说,它们不会被共享,也不会以任何方式与其它不同实例中的相同名称的字段产生关联。
class Robot:
# 一个类变量,用来计数机器人的数量
population = 0
def __init__(self, name):
self.name = name
print("(Initializing {})".format(self.name))
# 当有人被创建时,机器人
# 将会增加人口数量
Robot.population += 1
def die(self):
print("{} is being destroyed!".format(self.name))
Robot.population -= 1
if Robot.population == 0:
print("{} was the last one.".format(self.name))
else:
print("There are still {:d} robots working.".format(Robot.population))
def say_hi(self):
print("Greetings, my masters call me{}.".format(self.name))
@classmethod
def how_many(cls):
print("We have {:d} robots.".format(cls.population))
droid1 = Robot("R2-D2")
droid1.say_hi()
Robot.how_many()
droid2 = Robot("C-3PO")
droid2.say_hi()
Robot.how_many()
print("\nRobots can do some work here.\n")
print("Robots have finished their work. So let's destroy them.")
droid1.die()
droid2.die()
Robot.how_many()
#输出
#(Initializing R2-D2)
#Greetings, my masters call me R2-D2.
#We have 1 robots.
#(Initializing C-3PO)
#Greetings, my masters call me C-3PO.
#We have 2 robots.
#Robots can do some work here.
#Robots have finished their work. So let's destroy them.
#R2-D2 is being destroyed!
#There are still 1 robots working.
#C-3PO is being destroyed!
#C-3PO was the last one.
#We have 0 robots.
- 在本例中, population 属于 Robot 类,因此它是一个类变量。 name 变量属于一个对象(通过使用 self 分配),因此它是一个对象变量。
- 因此,我们通过 Robot.population 而非 self.population 引用 population 类变量。我们对于 name 对象变量采用 self.name 标记法加以称呼,这是这个对象中所具有的方法。要记住这个类变量与对象变量之间的简单区别。同时你还要注意当一个对象变量与一个类变量名称相同时,类变量将会被隐藏。
- how_many 实际上是一个属于类而非属于对象的方法。这就意味着我们可以将它定义为一个classmethod(类方法) 或是一个 staticmethod(静态方法) ,这取决于我们是否知道我们需不需要知道我们属于哪个类。由于我们已经引用了一个类变量,因此我们使用 classmethod(类方法) 。
- 我们使用装饰器(Decorator)将 how_many 方法标记为类方法。你可以将装饰器想象为调用一个包装器(Wrapper)函数的快捷方式,因此启用@classmethod 装饰器等价于调用:how_many = classmethod(how_many)
- 所有的类成员都是公开的。但有一个例外:如果你使用数据成员并在其名字中使用双下划线作为前缀,形成诸如 __privatevar 这样的形式,Python 会使用名称调整(Name-mangling)来使其有效地成为一个私有变量。因此,你需要遵循这样的约定:任何在类或对象之中使用的变量其命名应以下划线开头,其它所有非此格式的名称都将是公开的,并可以为其它任何类或对象所使用。
(所有类成员包括数据成员都是public类型,并且 Python 中所有的方法都是virtual虚方法)
继承
面向对象编程的一大优点是对代码的重用,重用的一种实现方法就是通过继承机制。
class SchoolMember:
'''代表任何学校里的成员。'''
def __init__(self, name, age):
self.name = name
self.age = age
print('(Initialized SchoolMember: {})'.format(self.name))
def tell(self):
'''告诉我有关我的细节。'''
print('Name:"{}" Age:"{}"'.format(self.name, self.age), end=" ")
class Teacher(SchoolMember):
'''代表一位老师。'''
def __init__(self, name, age, salary):
SchoolMember.__init__(self, name, age)
self.salary = salary
print('(Initialized Teacher: {})'.format(self.name))
def tell(self):
SchoolMember.tell(self)
print('Salary: "{:d}"'.format(self.salary))
class Student(SchoolMember):
'''代表一位学生。'''
def __init__(self, name, age, marks):
SchoolMember.__init__(self, name, age)
self.marks = marks
print('(Initialized Student: {})'.format(self.name))
def tell(self):
SchoolMember.tell(self)
print('Marks: "{:d}"'.format(self.marks))
t = Teacher('Mrs. Shrividya', 40, 30000)
s = Student('Swaroop', 25, 75)
# 打印一行空白行
print()
members = [t, s]
for member in members:
# 对全体师生工作
member.tell()
#输出
#(Initialized SchoolMember: Mrs. Shrividya)
#(Initialized Teacher: Mrs. Shrividya)
#(Initialized SchoolMember: Swaroop)
#(Initialized Student: Swaroop)
#Name:"Mrs. Shrividya" Age:"40" Salary: "30000"
#Name:"Swaroop" Age:"25" Marks: "75"
例子中teacher和student即schoolmate的子类,由于python中默认虚方法。所以调用子类而非父类的方法。
具体有关多态部分的内容可参考:SV中的OOP四、类的三要素3.多态
三、输入与输出
用户输入内容
def reverse(text):
return text[::-1]
def is_palindrome(text):
return text == reverse(text)
something = input("Enter text: ")
if is_palindrome(something):
print("Yes, it is a palindrome")
else:
print("No, it is not a palindrome")
#输出
#Enter text: sir
#No, it is not a palindrome
#Enter text: madam
#Yes, it is a palindrome
- 使用切片功能翻转文本。提供第三个参数来确定切片的步长(Step)。默认的步长为 1 ,它会返回一份连续的文本。如果给定一个负数步长,如 -1 ,将返回翻转过的文本。
- input() 函数可以接受一个字符串作为参数,并将其展示给用户。尔后它将等待用户输入内容或敲击返回键。一旦用户输入了某些内容并敲下返回键, input() 函数将返回用户输入的文本。我们获得文本并将其进行翻转。如果原文本与翻转后的文本相同,则判断这一文本是回文。
文件
可以通过创建一个属于 file 类的对象并适当使用它的 read 、 readline 、 write 方法来打开或使用文件,并对它们进行读取或写入。读取或写入文件的能力取决于你指定以何种方式打开文件。最后,当你完成了文件,你可以调用 close 方法来告诉 Python 已经完成了对该文件的使用。
poem = '''\
Programming is fun
When the work is done
if you wanna make your work also fun:
use Python!
'''
# 打开文件以编辑('w'riting)
f = open('poem.txt', 'w')
# 向文件中编写文本
f.write(poem)
# 关闭文件
f.close()
# 如果没有特别指定,
# 将假定启用默认的阅读('r'ead)模式
f = open('poem.txt')
while True:
line = f.readline()
# 零长度指示 EOF
if len(line) == 0:
break
# 每行(`line`)的末尾
# 都已经有了换行符
#因为它是从一个文件中进行读取的
print(line, end='')
# 关闭文件
f.close()
#输出
#Programming is fun
#When the work is done
#if you wanna make your work also fun:
# use Python!
- 首先,我们使用内置的 open 函数并指定文件名以及我们所希望使用的打开模式来打开一个文件。打开模式可以是阅读模式( ‘r’ ),写入模式( ‘w’ )和追加模式( ‘a’ )。我们还可以选择是通过文本模式( ‘t’ )还是二进制模式( ‘b’ )来读取、写入或追加文本。在默认情况下, open() 会将文件视作文本(text)文件,并以阅读(read)模式打开它。
- 在我们的案例中,我们首先采用写入模式打开文件并使用文件对象的 write 方法来写入文件,并在最后通过 close 关闭文件。
- 接下来,我们重新在阅读模式下打开同一个文件。我们不需要特别指定某种模式,因为“阅读文本文件”是默认的。我们在循环中使用 readline 方法来读取文件的每一行。这一方法将会一串完整的行,其中在行末尾还包含了换行符。当一个空字符串返回时,它表示我们已经到达了文件末尾,并且通过 break 退出循环。
- 最后,我们最终通过 close 关闭了文件。
Pickle
Python 提供了一个叫作 Pickle 的标准模块,通过它你可以将任何纯 Python 对象存储到一个文件中,并在稍后将其取回。这叫作持久地(Persistently)存储对象。
import pickle
# The name of the file where we will store the object
shoplistfile = 'shoplist.data'
# The list of things to buy
shoplist = ['apple', 'mango', 'carrot']
# Write to the file
f = open(shoplistfile, 'wb')
# Dump the object to a file
pickle.dump(shoplist, f)
f.close()
# Destroy the shoplist variable
del shoplist
# Read back from the storage
f = open(shoplistfile, 'rb')
# Load the object from the file
storedlist = pickle.load(f)
print(storedlist)
#输出
#['apple', 'mango', 'carrot']
- 要想将一个对象存储到一个文件中,我们首先需要通过 open 以写入(write)二进制(binary)模式打开文件,然后调用 pickle 模块的 dump 函数。这一过程被称作封装(Pickling)。
- 接着,我们通过 pickle 模块的 load 函数接收返回的对象。这个过程被称作拆封(Unpickling)。
Unicode
我们阅读或写入某一文件时,需要将我们的 Unicode 字符串转换至“UTF-8”格式。
import io
f = io.open("abc.txt", "wt", encoding="utf-8")
f.write(u"Imagine non-English language here")
f.close()
text = io.open("abc.txt", encoding="utf-8").read()
print(text)
四、异常
错误
如果把 print 误拼成 Print ,Python 会抛出(Raise)一个语法错误同时 Python 还会打印出检测到的错误发生的位置。这就是一个错误错误处理器(Error Handler) 为这个错误所做的事情。。
>>> Print("Hello World")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'Print' is not defined
>>> print("Hello World")
Hello World
处理异常
可以通过使用 try…except 来处理异常状况。一般来说我们会把通常的语句放在 try 代码块中,将我们的错误处理器代码放置在 except 代码块中。
try:
text = input('Enter something --> ')
except EOFError:
print('Why did you do an EOF on me?')
except KeyboardInterrupt:
print('You cancelled the operation.')
else:
print('You entered {}'.format(text))
#输出
#Press ctrl + d
#Enter something --> Why did you do an EOF on me?
#Press ctrl + c
#Enter something --> You cancelled the operation.
#Enter something --> No exceptions
#You entered No exceptions
- 我们将所有可能引发异常或错误的语句放在 try 代码块中,并将相应的错误或异常的处理器(Handler)放在 except 子句或代码块中。 except 子句可以处理某种特定的错误或异常,或者是一个在括号中列出的错误或异常。如果没有提供错误或异常的名称,它将处理所有错误与异常。
- 如果没有任何错误或异常被处理,那么将调用 Python 默认处理器,它只会终端程序执行并打印出错误信息。我们已经在前面的章节里见过了这种处理方式。
- 你还可以拥有一个 else 子句与 try…except 代码块相关联。 else 子句将在没有发生异常的时候执行。
抛出异常
可以通过 raise 语句来引发一次异常,具体方法是提供错误名或异常名以及要抛出异常的对象。
你能够引发的错误或异常必须是直接或间接从属于 Exception (异常) 类的派生类。
class ShortInputException(Exception):
'''一个由用户定义的异常类'''
def __init__(self, length, atleast):
Exception.__init__(self)
self.length = length
self.atleast = atleast
try:
text = input('Enter something --> ')
if len(text) < 3:
raise ShortInputException(len(text), 3)
# 其他工作能在此处继续正常运行
except EOFError:
print('Why did you do an EOF on me?')
except ShortInputException as ex:
print(('ShortInputException: The input was ' +
'{0} long, expected at least {1}')
.format(ex.length, ex.atleast))
else:
print('No exception was raised.')
#输出
#Enter something --> a
#ShortInputException: The input was 1 long, expected at least 3
#Enter something --> abc
#No exception was raised.
- 在本例中,我们创建了我们自己的异常类型。这一新的异常类型叫作 ShortInputException 。它包含两个字段——获取给定输入文本长度的 length ,程序期望的最小长度 atleast 。
- 在 except 子句中,我们提及了错误类,将该类存储 as(为) 相应的错误名或异常名。这类似于函数调用中的形参与实参。在这个特殊的 except 子句中我们使用异常对象的 length与 atlease 字段来向用户打印一条合适的信息。
Try … Finally
假设你正在你的读取中读取一份文件。可以通过 finally 块来确保文件对象被正确关闭。
import sys
import time
f = None
try:
f = open("poem.txt")
# 我们常用的文件阅读风格
while True:
line = f.readline()
if len(line) == 0:
break
print(line, end='')
sys.stdout.flush()
print("Press ctrl+c now")
# 为了确保它能运行一段时间
time.sleep(2)
except IOError:
print("Could not find file poem.txt")
except KeyboardInterrupt:
print("!! You cancelled the reading from the file.")
finally:
if f:
f.close()
print("(Cleaning up: Closed the file)")
#输出
#Programming is fun
#Press ctrl+c now
#!! You cancelled the reading from the file.
#(Cleaning up: Closed the file)
- 我们按照通常文件读取进行操作,但是我们同时通过使用 time.sleep 函数任意在每打印一行后插入两秒休眠,使得程序运行变得缓慢(在通常情况下 Python 运行得非常快速)。当程序在处在运行过过程中时,按下 ctrl + c 来中断或取消程序。
- 你会注意到 KeyboardInterrupt 异常被抛出,尔后程序退出。不过,在程序退出之前,finally子句得到执行,文件对象总会被关闭。
- 另外要注意到我们在 print 之后使用了 sys.stout.flush() ,以便它能被立即打印到屏幕上。
with 语句
在 try 块中获取资源,然后在 finally 块中释放资源是一种常见的模式。因此,还有一个with 语句使得这一过程可以以一种干净的姿态得以完成。
with open("poem.txt") as f:
for line in f:
print(line, end='')
- 程序输出的内容应与上一个案例所呈现的相同。本例的不同之处在于我们使用的是 open 函数与 with 语句——我们将关闭文件的操作交由 with open 来自动完成。
- 在幕后发生的事情是有一项 with 语句所使用的协议。它会获取由 open 语句返回的对象,在本案例中就是“thefile”。
- 它总会在代码块开始之前调用 thefile.enter 函数,并且总会在代码块执行完毕之后调用 thefile.exit 。
- 因此,我们在 finally 代码块中编写的代码应该格外留心 exit 方法的自动操作。这能够帮助我们避免重复显式使用 try…finally 语句。
五、标准库
sys 模块
sys 模块包括了一些针对特定系统的功能。
比如我们需要检查正在使用的 Python 软件的版本, sys 模块会给我们相关的信息。
>>> import sys
>>> sys.version_info
sys.version_info(major=3, minor=5, micro=1, releaselevel='final', serial=0)
>>> sys.version_info.major == 3
True
sys 模块包含一个 version_info 元组,它提供给我们版本信息。第一个条目是主版本信息。我们可以调出这些信息并使用它。
日志模块
如果你想将一些调试信息或一些重要的信息储存在某个地方,以便你可以检查你的程序是否如你所期望那般运行,可以通过 logging 模块来实现。
import os
import platform
import logging
if platform.platform().startswith('Windows'):
logging_file = os.path.join(os.getenv('HOMEDRIVE'),
os.getenv('HOMEPATH'),
'test.log')
else:
logging_file = os.path.join(os.getenv('HOME'),
'test.log')
print("Logging to", logging_file)
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s : %(levelname)s : %(message)s',
filename=logging_file,
filemode='w',
)
logging.debug("Start of the program")
logging.info("Doing something")
logging.warning("Dying now")
#输出
#Logging to /Users/swa/test.log
#2014-03-29 09:27:36,660 : DEBUG : Start of the program
#2014-03-29 09:27:36,660 : INFO : Doing something
#2014-03-29 09:27:36,660 : WARNING : Dying now
- 我们使用了三款标准库中的模块—— os 模块用以和操作系统交互, platform 模块用以获取平台——操作系统——的信息, logging 模块用来记录(Log)信息。
- 首先,我们通过检查 platform.platform() 返回的字符串来确认我们正在使用的操作系统。如果它是 Windows,我们将找出其主驱动器(Home Drive),主文件夹(Home Folder)以及我们希望存储信息的文件名。将这三个部分汇聚到一起,我们得到了有关文件的全部位置信息。
- 使用 os.path.join() 函数来将这三部分位置信息聚合到一起。使用这一特殊函数,而非仅仅将这几段字符串拼凑在一起的原因是这个函数会确保完整的位置路径符合当前操作系统的预期格式。
- 然后我们配置 logging 模块,让它以特定的格式将所有信息写入我们指定的文件。
- 最后,无论这些信息是用以调试,提醒,警告甚至是其它关键的消息,我们都可以将其聚合并记录。一旦程序开始运行,我们可以检查这一文件,从而我们便能知道程序运行过程中究竟发生了什么,哪怕在用户运行时什么信息都没有显示。
六、其它
传递元组
使用一个元组从一个函数中返回两个不同的值。
>>> def get_error_details():
... return (2, 'details')
...
>>> errnum, errstr = get_error_details()
>>> errnum
2
>>> errstr
'details'
要注意到 a, b = 的用法会将表达式的结果解释为具有两个值的一个元
组。
这也意味着在 Python 中交换两个变量的最快方法是:
>>> a = 5; b = 8
>>> a, b
(5, 8)
>>> a, b = b, a
>>> a, b
(8, 5)
单语句块
每一个语句块都由其自身的缩进级别与其它部分相区分。但是如果你的语句块只包括单独的一句语句,那么你可以在同一行指定它,例如条件语句与循环语句。
>>> flag = True
>>> if flag: print('Yes')
...
Yes
Lambda 表格
lambda 语句可以创建一个新的函数对象。从本质上说, lambda 需要一个参数,后跟一个表达式作为函数体,这一表达式执行的值将作为这个新函数的返回值。
points = [{'x': 2, 'y': 3},
{'x': 4, 'y': 1}]
points.sort(key=lambda i: i['y'])
print(points)
#输出
#[{'y': 1, 'x': 4}, {'y': 3, 'x': 2}]
一个 list 的 sort 方法可以获得一个 key 参数,用以决定列表的排序方式(通常我们只知道升序与降序)。在我们的案例中,我们希望进行一次自定义排序,为此我们需要编写一个函数,但是又不是为函数编写一个独立的 def 块,只在这一个地方使用,因此我们使用 Lambda 表达式来创建一个新函数。
列表推导
列表推导用于从一份现有的列表中得到一份新列表。
比如现在你已经有了一份数字列表,你想得到一个相应的列表,其中的数字在大于 2 的情况下将乘以2:
listone = [2, 3, 4]
listtwo = [2*i for i in listone if i > 2]
print(listtwo)
#输出
#[6, 8]
使用列表推导的优点在于,当我们使用循环来处理列表中的每个元素并将其存储到新的列表中时时,它能减少样板代码的数量。
在函数中接收元组与字典
有一种特殊方法,即分别使用 * 或 ** 作为元组或字典的前缀,来使它们作为一个参数为函数所接收。当函数需要一个可变数量的实参时,这将颇为有用。
>>> def powersum(power, *args):
... '''Return the sum of each argument raised to the specified power.'''
... total = 0
... for i in args:
... total += pow(i, power)
... return total
...
>>> powersum(2, 3, 4)
25#3x3+4x4
>>> powersum(2, 10)
100#10x10
因为我们在 args 变量前添加了一个 * 前缀,函数的所有其它的额外参数都将传递到args 中,并作为一个元组予以储存。如果采用的是 ** 前缀,则额外的参数将被视为字典的键值—值配对。
assert 语句
assert 语句用以断言某事是真的。如果其不是真的,就抛出一个错误。当语句断言失败时,将会抛出 AssertionError 。
>>> mylist = ['item']
>>> assert len(mylist) >= 1
>>> mylist.pop()
'item'
>>> assert len(mylist) >= 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError
装饰器
装饰器(Decorators)是应用包装函数的快捷方式。这有助于将某一功能与一些代码一遍又一遍地“包装”。举个例子,我为自己创建了一个 retry 装饰器,这样我可以将其运用到任何函数之中,如果在一次运行中抛出了任何错误,它就会尝试重新运行,直到最大次数 5 次,并且每次运行期间都会有一定的延迟。这对于你在对一台远程计算机进行网络调用的情况十分有用:
from time import sleep
from functools import wraps
import logging
logging.basicConfig()
log = logging.getLogger("retry")
def retry(f):
@wraps(f)
def wrapped_f(*args, **kwargs):
MAX_ATTEMPTS = 5
for attempt in range(1, MAX_ATTEMPTS + 1):
try:
return f(*args, **kwargs)
except:
log.exception("Attempt %s/%s failed : %s",
attempt,
MAX_ATTEMPTS,
(args, kwargs))
sleep(10 * attempt)
log.critical("All %s attempts failed : %s",
MAX_ATTEMPTS,
(args, kwargs))
return wrapped_f
counter = 0
@retry
def save_to_database(arg):
print("Write to a database or make a network call or etc.")
print("This will be automatically retried if exception is thrown.")
global counter
counter += 1
# 这将在第一次调用时抛出异常
# 在第二次运行时将正常工作(也就是重试)
if counter < 2:
raise ValueError(arg)
if __name__ == '__main__':
save_to_database("Some bad value")
#输出
#Write to a database or make a network call or etc.
#This will be automatically retried if exception is thrown.
#ERROR:retry:Attempt 1/5 failed : (('Some bad value',), {})
#Traceback (most recent call last):
# File "more_decorator.py", line 14, in wrapped_f
# return f(*args, **kwargs)
# File "more_decorator.py", line 39, in save_to_database
# raise ValueError(arg)
#ValueError: Some bad value
#Write to a database or make a network call or etc.
#This will be automatically retried if exception is thrown.