Python02进阶

Python进阶

词典 dictionary

创建词典

1
2
3
4
dic = {'tom':11,'sam':45,'lily':32}
print(dic) # {'tom': 11, 'sam': 45, 'lily': 32}
print(type(dic)) # <class 'dict'>
print(dic['sam']) # 45

词典和表类似的地方,是包含有多个元素,每个元素以逗号分隔。但词典的元素包含有两部分,,常见的是以字符串来表示键,也可以使用数字等表示键(不可变的对象可以作为键)。值可以是任意对象。键和值两者一一对应。

与表不同的是,词典的元素没有顺序。你不能通过下标引用元素。词典是通过键来引用

词典元素的循环调用

1
2
3
4
5
dic = {'tom':11,'sam':45,'lily':32}

for key in dic: # 循环调用
print(key)
print(dic[key])

在循环中,dict的每个键,被提取出来,赋予给key变量。

词典的常用方法

1
2
3
4
5
6
7
dic = {'tom':11,'sam':45,'lily':32}

print(dic.keys()) # 返回dic所有的键 dict_keys(['tom', 'sam', 'lily'])
print(dic.values()) # 返回dic所有的值 dict_values([11, 45, 32])
print(dic.items()) # 返回dic所有的元素(键值对)dict_items([('tom', 11), ('sam', 45), ('lily', 32)])
del dic['tom'] # 删除 dic 的‘tom’元素
print(len(dic)) # 查询词典中的元素总数。

文本文件的输入输出

Python具有基本的文本文件读写功能。Python的标准库提供有更丰富的读写功能。

文本文件的读写主要通过**open()**所构建的文件对象来实现。

创建文件对象

1
对象名 = open(文件名,模式)
1
2
3
4
5
6
7
8
模式如下:
- r 打开只读文件,该文件必须存在。
- r+ 打开可读写的文件,该文件必须存在。
- w 打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件。
- w+ 打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。
- a 以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。
- a+ 以附加方式打开可读写的文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾后,即文件原先的内容会被保留。
- 上述的形态字符串都可以再加一个b字符,如rb、w+b或ab+等组合,加入b 字符用来告诉函数库打开的文件为二进制文件,而非纯文字文件。
1
2
# 如:
f = open('test.txt','r+',encoding='utf-8')

文件对象的方法

1
2
3
4
5
6
7
8
9
10
11
12
f = open('test.txt','r+',encoding='utf-8')

# 读取
content = f.read(N) # 读取N bytes的数据
content = f.readline() # 读取一行
content = f.readlines() # 读取所有行,储存在列表中,每个元素是一行。

# 写入
f.write('I like apple!\n') # 将'I like apple'写入文件并换行

# 关闭文件
f.close()

模块引入

1
2
3
4
5
6
7
8
# first.py
def laugh():
print 'HaHaHaHa'

# second.py
import first #将first文件引入
for i in range(10):
first.laugh()

second.py中,使用first.py中定义的laugh()函数。

引入模块后,可以通过 模块.对象 的方式来调用引入模块中的某个对象。上面例子中,first 为引入的模块,laugh() 是我们所引入的对象。

Python中还有其它的引入方式:

1
2
3
4
5
6
7
8
9
10
11
# 引入模块a,并将模块a重命名为b
import a as b

# 从模块a中引入function1对象。调用a中对象时,我们不用再说明模块,即直接使用function1,而不是a.function1。
from a import function1

# 从模块a中引入所有对象。调用a中对象时,我们不用再说明模块,即直接使用对象,而不是a.对象。
from a import *

# 包含文件层级
import dir.a as b

模块包

可以将功能相似的模块放在同一个文件夹(比如说this_dir)中,构成一个模块包。通过

1
import this_dir.module	# 引入this_dir文件夹中的module模块。

该文件夹中必须包含一个 __init__.py 的文件,提醒Python,该文件夹为一个模块包。__init__.py 可以是一个空文件

函数的参数传递

函数(function)的参数(arguments)传递。根据位置,传递对应的参数。Python还支持更多的参数传递方式。

关键字传递

关键字(keyword)传递是根据每个参数的名字传递参数。关键字并不用遵守位置的对应关系。依然沿用上面f的定义,更改调用方式:

1
2
3
4
print(f(c=3,b=2,a=1))

# 关键字传递可以和位置传递混用。但位置参数要出现在关键字参数之前:
print(f(1,c=3,b=2))

参数默认值

定义函数的时候,使用形如a=19的方式,可以给参数赋予默认值(default)。如果该参数最终没有被传递值,将使用该默认值。

1
2
3
4
5
def f(a,b,c=10):
return a+b+c

print(f(3,2)) # 15 c没有被赋值,c将使用默认值10.
print(f(3,2,1)) # 6 c被赋值为1,不再使用默认值。

包裹传递

定义函数时,我们有时候并不知道调用的时候会传递多少个参数。这时候,包裹(packing)位置参数,或者包裹关键字参数,来进行参数传递,会非常有用。

1
2
3
4
5
6
def func(*name):
print type(name)
print name

func(1,4,6) # <class 'tuple'> /n (1, 4, 5)
func(5,6,7,1,2,3) # <class 'tuple'> /n (5,6,7,1,2,3)

在func的参数表中, 所有的参数被name收集, 根据位置合并成一个元组(tuple), 赋值给name, 这就是包裹传递

1
2
3
4
5
6
def func(**dict):
print type(dict)
print dict

func(a=1,b=9) # <class 'dict'> /n {'a': 1, 'b': 2}
func(m=2,n=1,c=11) # <class 'dict'> /n {'m': 1, 'n': 4, 'c': 22}

与上面一个例子类似,dict是一个字典,收集所有的关键字,传递给函数func。为了提醒Python,参数dict是包裹关键字传递所用的字典,在dict前加 **

即 包裹传递的关键在于定义函数时,在相应元组或字典前加* **

解包裹

***,也可以在调用的时候使用,即解包裹(unpacking), 下面为例:

1
2
3
4
5
def func(a,b,c):
print a,b,c

args = (1,3,4)
func(*args)

在传递tuple时,让tuple的每一个元素对应一个位置参数。在调用func时使用 * ,是为了提醒Python:要把args拆成分散的三个元素,分别传递给a,b,c

相应的,也存在对词典的解包裹,使用相同的func定义

1
2
3
4
5
def func(a,b,c):
print a,b,c

dict = {'a':1,'b':2,'c':3}
func(**dict)

在传递词典dict时,让词典的每个键值对作为一个关键字传递给func。

混合

在定义或者调用参数时,参数的几种传递方式可以混合。但在过程中要小心前后顺序。基本原则是:先位置,再关键字,再包裹位置,再包裹关键字

循环设计

range()

1
2
3
4
str = 'abcdefghijk'
for i in range(0,len(str)):
print(i) # 当前索引
print(str[i]) # 索引对应的元素

我们利用 len() 函数和 range() 函数,用 i 作为 S 序列的下标来控制循环。在range函数中,可以分别定义上限,下限和每次循环的步长。

enumerate()

利用enumerate()枚举函数,可以在每次循环中同时得到下标和元素:

1
2
3
4
str = 'abcdefghijk'
for (index,char) in enumerate(str):
print(index) # 当前索引
print(char) # 索引对应的元素
1
2
3
4
5
# 枚举统计文件行数
count = 0
for index,line in enumerate(open('record.txt','r',encoding='utf-8')):
count +=1
print(count)

enumerate()在每次循环中,返回的是一个包含两个元素的定值表(tuple),两个元素分别赋予index和char。

zip()

多个等长的序列,然后想要每次循环时从各个序列分别取出一个元素,可以利用zip()

zip()函数的功能,就是从多个列表中,依次各取出一个元素。每次取出的(来自不同列表的)元素合成一个元组,合并成的元组放入zip()返回的列表中。zip()函数起到了聚合列表的功能。

1
2
3
4
5
6
tupleA = [1,2,3]
tupleB = [0,9,8,7]
tupleC = ['a','b','c','d','e']
for a,b,c in zip(tupleA,tupleB,tupleC):
# 每次循环时,从各个序列分别从左到右取出一个元素,合并成一个tuple,然后tuple的元素赋予给a,b,c 。
print(a,b,c)

image-20220610172139676

zip_longest()

1
2
3
4
5
6
7
# zip_longest() 最长列表为基准,空值指定为0而不是None
tupleA = [1,2,3]
tupleB = [0,9,8,7]
tupleC = ['a','b','c','d','e']
from itertools import zip_longest
for a,b,c in zip_longest(tupleA,tupleB,tupleC,fillvalue=0):
print(a,b,c)

image-20220610172617045

可以分解聚合后的列表,如下:

1
2
3
4
5
6
7
# 分解聚合后的列表
tupleA = [1,2,3]
tupleB = [0,9,8]
zipped = zip(tupleA,tupleB) # 聚合cluster
print(zipped)
na,nb = zip(*zipped) # 解包unpack
print(na,nb)

image-20220610172658993

可迭代对象

迭代对象

迭代对象是一个包含有 __iter__ 方法的对象。

一个可迭代对象的 __iter__ 方法实际上返回的一个迭代器,并且如期响应 next 方法。而在迭代完成后,再次使用 next 时,抛出 StopIteration错误

列表其实也是一个迭代对象,包含有 __iter__方法。在我们使用 iter(lst) 返回一个迭代器 test_iter 后,使用 next()方法每次迭代一个元素,到迭代完成后继续使用 next 抛出StopIteration。

1
2
3
4
5
6
7
8
9
10
list1 = [1,2,3,4,5]
test_iter = iter(list1)
print(type(test_iter)) # class list_iterator

print(next(test_iter))
print(next(test_iter))
print(next(test_iter))
print(next(test_iter))
print(next(test_iter))
print(next(test_iter)) # StopIteration

image-20220610173017166

迭代器

迭代器是包含 __next__ 方法的任何对象,(因为包含 __next__ 方法,因此能响应 next 方法)

生成器也是一种迭代器。

生成器的编写方法和函数定义类似,只是在 return 的地方改为 yield。生成器中可以有多个 yield。当生成器遇到一个 yield 时,会暂停运行生成器,返回 yield 后面的值。当再次调用生成器的时候,会从刚才暂停的地方继续运行,直到下一个 yield。每次使用一个 yield 返回的值。

1
2
3
4
5
6
7
8
9
10
11
# 生成器 该生成器共有三个yield, 如果用作迭代器时,会进行三次循环。
def genera():
a = 100
yield a
a = a * 8
yield a
yield 1000

print(genera()) # generator object
for i in genera():
print(i)

image-20220610173231580

可以写成**生成器表达式(Generator Expression)**:

1
2
3
4
5
def genera2():
for i in range(4):
yield i

genera3 = (x for x in range(4))

image-20220610173601786

列表推导

列表推导(list comprehension)是快速生成列表的方法。语法简单,很有实用价值。

1
2
3
4
5
6
7
list1 = []
for i in range(10):
list1.append(i**2)
print(list1)

list1 = [x**2 for x in range(10)] # 快捷写法 与生成器表达式类似,只不过用的是中括号
print(list1)

image-20220610173855613

函数对象

函数也是一个对象,具有属性(可以使用dir()查询)。作为对象,它还可以赋值给其它对象名,或者作为参数传递。

lambda函数

lambda生成一个函数对象。该函数参数为x,y,返回值为x+y。函数对象赋给func。func的调用与正常函数无异。

1
2
3
4
5
def func1(x,y):
return x+y

func = lambda x,y: x+y
# 以上两个函数功能相同

image-20220610174230533

函数作为参数传递

函数可以作为一个对象,进行参数传递。函数名(比如func)即该对象

1
2
3
4
5
6
7
8
9
func = lambda x,y: x+y	# 定义函数

def func2(f,a,b): # 将函数作为参数传入
print('test')
print(f(a,b)) # 调用函数

# 下面两种写法相同
func2(func,2,8)
func2((lambda x,y: x+y), 4,7)

image-20220610174550100

map()函数

map()的功能是将函数对象依次作用于表的每一个元素,返回表存储每次作用的结果, 它的第一个参数是一个函数对象。

1
2
3
test = map((lambda x: x**2),[1,2,3,4,5])

test2 = map((lambda x,y:x+y),[1,2,3],[4,5,6])

image-20220610174907505

filter()函数

filter函数的第一个参数也是一个函数对象。它也是将作为参数的函数对象作用于多个元素。如果函数对象返回的是True,则该次的元素被储存于返回的表中。 filter通过读入的函数来筛选数据。

1
2
3
4
5
6
def bigger(a):
if a>100:
return True
else:
return False
bigNumber = filter(bigger,[10,55,1000,300,20,487])

image-20220610175047285

reduce()函数

reduce函数的第一个参数也是函数,但有一个要求,就是这个函数自身能接收两个参数。reduce可以累进地将函数作用于各个参数。如下例:

1
2
3
from functools import reduce
red = reduce((lambda x,y:x+y),[1,2,3,4,5])
print(red) # (((1+2)+3)+4)+5

image-20220610175251970

1
2
3
4
5
reduce的第一个参数是lambda函数,它接收两个参数x,y, 返回x+y。

educe将表中的前两个元素(1和2)传递给lambda函数,得到3。
该返回值(3)将作为lambda函数的第一个参数,而表中的下一个元素(3)作为lambda函数的第二个参数,进行下一次的对lambda函数的调用,得到6。
依次调用lambda函数,每次lambda函数的第一个参数是上一次运算结果,而第二个参数为表中的下一个元素,直到表中没有剩余元素。

错误处理

异常处理

try-except语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
try:
...
except exception1:
...
except exception2:
...
except:
...
finally:
...
# 如果try中有异常发生时,将执行异常的归属,执行except。
# 异常层层比较,看是否是exception1, exception2...,直到找到其归属,执行相应的except中的语句。
# 如果except后面没有任何参数,那么表示所有的exception都交给这段程序处理。比如:

报错案例

1
2
3
list_iter = iter(range(5))
for i in range(100):
print(next(list_iter)) # 报错: StopIteration

image-20220610175510177

使用try-except。在try程序段中,我们放入容易犯错的部分。我们可以跟上except,来说明如果在try部分的语句发生StopIteration时,程序该做的事情。如果没有发生异常,则except部分被跳过。

1
2
3
4
5
6
list_iter = iter(range(5))
try:
for i in range(100):
print(next(list_iter))
except StopIteration:
print('it is the end')

image-20220610175640658

报错流程: try -> 异常 -> except -> finally

如果无法将异常交给合适的对象,异常将继续向上层抛出,直到被捕捉或者造成主程序报错。比如下面的程序:

1
2
3
4
5
6
7
8
9
10
def test_func():
try:
m = 1/0
except NameError:
print('Catch NameError in Sub Function') # 未定义变量

try:
test_func()
except ZeroDivisionError:
print('Catch Error in Main Program')

image-20220610180022951

抛出异常

也可以使用raise自己主动抛出异常

1
raise StopIteration # 主动抛出异常

image-20220610180308839

动态类型

动态类型(dynamic typing)。即Python的变量(variable)不需要声明,而在赋值时,变量可以重新赋值为任意值。这些都与动态类型的概念相关。

动态类型

引用和对象分离,是动态类型的核心。引用可以随时指向一个新的对象:

1
2
a = 3	# 通过赋值,引用a指向对象3。
a = 'at'

第一个语句中,3是储存在内存中的一个整数对象。通过赋值,引用a指向对象3

第二个语句中,内存中建立对象‘at’,是一个字符串(string)。引用a指向了’at’。此时,对象3不再有引用指向它。Python会自动将没有引用指向的对象销毁(destruct),释放相应内存。

对于小的整数和短字符串,Python会缓存这些对象,而不是频繁的建立和销毁。

1
2
3
4
a = 5
b = a
a = a + 2
print(a,b) # 7 5

即使是多个引用指向同一个对象,如果一个引用值发生变化,那么实际上是让这个引用指向一个新的引用,并不影响其他的引用的指向。从效果上看,就是各个引用各自独立,互不影响。

1
2
3
4
5
6
7
8
9
L1 = [1,2,3]
L2 = L1
L1 = 2
print(L1,L2) # 2 [1, 2, 3]

L1 = [1,2,3]
L2 = L1
L1[0] = 10
print(L1,L2) # [10, 2, 3] [10, 2, 3]

即列表(list)和字典(dictionary)可以通过引用其元素,改变自身对象(in-place change),称为: 可变数据对象(mutable object)

而数字/字符串, 不能改变对象本身, 只能改变引用指向, 称为不可变数据对象(immutable object)

元组(tuple)虽然可以调用引用元素, 但不可赋值, 所以也算不可变数据对象

从动态类型看函数的参数传递

函数的参数传递,本质上传递的是引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def func(x):
x = 100
print(x)

a = 1
func(a) # 100
print(a) # 1

def func1(x):
x[0] = 100
print(x)

b = [1,2,3]
func1(b) # [100, 2, 3]
print(b) # [100, 2, 3]

参数x是一个新的引用,指向a所指的对象。

如果参数是不可变(immutable)的对象,a和x引用之间相互独立。对参数x的操作不会影响引用a。

如果传递的是可变(mutable)的对象,那么改变函数参数,有可能改变原对象。所有指向原对象的引用都会受影响