Python学习笔记

一、Python学习笔记

1.1 列表

1.1.1 列表的方法

(1)增加列表元素的方法:(列表是可变的而字符串是不可变的)

x = "FishC"
y = "FishC"
s is y // True

x = ["FishC"]
y = ["FishC"]
x is y // False

// Append()在列表末尾增加单个元素、Extend()在列表末尾增加一个可迭代对象(如列表)
s = [1,2,3,4,5]
s[len(s):] = [6] //相当于s.Append(6)
s[len(s):] = [7,8,9] //相当于s.Extend([7,8,9])

(2)删除列表元素的方法:

//(1)remove()方法
list.remove("元素") //当这个元素在列表中存在多个时,只会删除第一个元素(下标最小的那个);

//(2)pop()方法
list.pop(2) //删除下标2位置上的元素(下标从0开始)

//(3)一次性清空列表所有元素
list.clear()

(3)插入列表元素的方法:

insert(arg1,arg2) // 第一个参数是插入的下标,第二个是插入的元素
s.insert(0,0) // 将元素0插入到列表的开头
s.insert(len(s),10) // 将元素10插入到列表的末尾

(4)列表的替换

//(1)替换单个元素
list[index] = "value" // 直接靠下标替换

//(2)替换多个连续的元素:靠切片来实现
heros[3:]= ["武松","林冲","李逵"] 
// 分为两步:
//(1)先将赋值号左边指定的内容删除;
//(2)再将赋值号右边的可迭代对象中的片段插入左边被删除的位置

heros = ["宋江","卢俊义","阮小二","阮小七","李逵","武松","高俅"]
heros[3:] = ["武松","林冲","李逵"]
for hero in heros:
    print(hero)

...
宋江 卢俊义 阮小二 武松 林冲 李逵 

(5)列表的排序

//(1)从小到大排序
nums.sort()
//(2)从大到小排序
nums.sort()
nums.reverse() //调换顺序
// 或者
nums.sort(reverse=True)

(6)列表中某元素的数量

nums.count(3)

(7)列表中某元素的索引值

nums.index(3) // 返回的是第一个找到的元素的下标值
nums.index(3,index1,index2) // 从指定范围查找元素下标值,前闭后开区间

(8)列表的拷贝

//(1)浅拷贝
nums1 = nums.copy()
nums2 = nums[:]  // 两句效果一样
// 修改nums不会改变nums1和nums2
// 浅拷贝只是拷贝了外层对象,嵌套列表的copy方法只拷贝了其引用
x = [[1,2,3],[4,5,6],[7,8,9]]
y = x.cpoy();
x[0][0] = 0 
x = [[0,2,3],[4,5,6],[7,8,9]]
y = [[0,2,3],[4,5,6],[7,8,9]]
// 或者import copy
import copy
y = copy.copy(x)
//(2)深拷贝
x = [[1,2,3],[4,5,6],[7,8,9]]
y = copy.deepcopy(x) // 将原对象进行拷贝的同时,对对象中所有引用的子对象也一并进行了拷贝
x[0][0] = 0
x = [[0,2,3],[4,5,6],[7,8,9]]
y = [[1,2,3],[4,5,6],[7,8,9]]

1.1.2 列表的加法和乘法

s = [1,2,3]
s * 3 = [1,2,3,1,2,3,1,2,3] // 重复列表内的所有元素若干次

1.1.3 列表推导式

expression for target in iterable
x = [1,2,3,4,5]
x = [i*2 for i in x] //将列表的元素乘2

// 将字符转化为Unicode编码
code = [ord(c) for c in "FishC"]
code = [70,105,115,104,67]

// 获取矩阵的第二列
matrix = [[1,2,3],[4,5,6],[7,8,9]]
col2 = [row[1] for row in matrix]
col2 = [2,5,8]
// 获取对角线
diag = [matrix[i][i] for i in range(len(matrix))]
diag = [1,5,9]
// 循环跟列表推导不一样,循环是通过迭代来逐个修改原列表的元素,而列表推导式则是创建一个新的列表,再赋值回原先的变量名

// 用列表推导式创建一个嵌套列表
s = [[0]*3 for i in range(3)]
// 0-9之间的偶数列表
even = [i for i in range(10) if i % 2 == 0] // 先执行for语句再执行if条件最后执行表达式

// 推导式的循环
flatten = [col for row in matrix for col in row]
flatten = [1,2,3,4,5,6,7,8,9]
// 相当于
flatten = []
for row in matrix:
    for col in row:
        flatten.append(col)

1.2 元组

1.2.1 元组的定义

元组用的是圆括号(或者不用括号,直接用逗号隔开),既能像列表那样同时容纳多种对象,也拥有字符串不可变的特性;
拼接(+)和重复(*)运算符也可以对元组进行操作
元组的嵌套直接用逗号

s = (1,2,3)
t = (4,5,6)
w = s,t
w = ((1,2,3),(4,5,6))

// 生成只有一个元素的元组
x = (520)  // type(x) <class 'int'>
x = (520,) // type(x) <class 'turple'>,不能忘了逗号

1.2.2 元组的打包和解包

t = (123,'FishC',3.14) // 打包
x,y,z = t // 解包
x = 123, y = 'FishC', z = 3.14 // 解包操作同样也适用于列表和字符串,赋值号左侧的变量名数量必须和右侧序列元素的数量一致,除非变量名前加*表示将剩余的值全赋给加星号的变量

1.2.3 元组的修改

s = [1,2,3]
t = [4,5,6]
w = (s,t)
w[0][0] = 0
w = ([0,2,3],[4,5,6]) // 元组中的元素是指向一个可变的列表,那么依然可以修改列表中的内容

1.3 字符串

1.3.1 回文数

x = "12321"
"是回文数" if x == x[::-1] else "不是回文数"

1.3.2 字符串的常用方法

capitalize()   // 整个字符串的首字母大写其余小写
title()        // 字符串每个单词的首字母都大写
swapcase()     // 字符串中的所有大小写翻转
upper()        // 所有字母都大写
casefold()     // 返回一个所有字母都小写的字符串,可以处理德语等其他语言
lower()        // 所有字母都小写,只能处理英语
// 默认空格填充,也可以第二个参数输入想要填充的字符(必须是长度为1的单个字符)
center(width)  // 居中,参数小于字符串长度则输出字符串,大于则填充空格使得字符串居中
ljust(width)   // 左对齐
rjust()        // 右对齐
zfill()        // 用0填充左侧
count("x",range1,range2) // 查找子字符串出现的次数
find("x")      // 从左往右查找子字符串的索引,找不到返回-1
rfind("x")     // 从右往左查找子字符串的索引,找不到返回-1
index("x")     // 也是返回索引,但是找不到会抛出异常
expandtabs([tabsize=8])  // 使用空格替换制表符,传入整数参数代表用几个空格代替tab
replace(old,new,count=-1) //默认-1替换所有,可以传入整数限定替换多少次
// 定义转换规则
table = str.maketrans(x,[,y[,z]])
translate(table)     // 转化
table = str.maketrans("ABCDEFG","1234567")  // 转换规则
"I love FishC".translate(table)  // 输出I love 6ish3
"I love FishC".translate(str.maketrans("ABCDEFG","1234567","love")) // 多加的参数表示忽略love这个字符串的转换,输出结果为 "I  6ish3"
// 返回bool的
startswith(prefix[,start[,end]])   // 可选参数,限定从起始和终止位置开始匹配,可以传入元组,只要存在一个匹配成功的就返回true
endswith(prefix[,start[,end]])  
istitle() // 一个字符串的所有单词的首字母都是大写则返回true
isupper() // 一个字符串的所有单词都是大写则返回true
isalpha() // 一个字符串全是字符(不包含空格)则返回true
isspace() // 一个字符串是不是全是空白字符串(包括tab,\n),是则返回true
isprintable() // 是否是可打印的(转义字符就不可打印),是则返回true
isprintable("I love you\n") //False
isprintable("I love you\\n") //True
isprintable(r"I love you\n") //True
isdigit()
// True: Unicode数字,byte数字(单字节),全角数字(双字节),罗马数字
// False: 汉字数字
// Error: 无

isdecimal()
// True: Unicode数字,,全角数字(双字节)
// False: 罗马数字,汉字数字
// Error: byte数字(单字节)

isnumeric()
// True: Unicode数字,全角数字(双字节),罗马数字,汉字数字
// False: 无
// Error: byte数字(单字节)

"520Fish".isidentifier() //False,不符合python的标识符
// 或者
import keyword
keyword.iskeyword("if") // True
lstrip(chars=None) // 左侧不要留白
rstrip(chars=None) // 右侧不要留白 
trip(chars=None)   // 去除左右空白,三个方法都可以传入字符,会去除包含在参数中的字符(匹配到任意一个字符都会去掉)
"www.ilovefishc.com".strip("wcom.") // 输出 ilovefish

// 删除指定的字符串前缀/后缀
removeprefix("www.")
removesuffix(".com")

// 拆分
partition() // 拆分为三元组
"www.ilovefishc.com".partition(".") // 输出 ('www','.','ilovefishc.com')
rpartition() // 从右往左找分隔符

// 切开所有的
split(sep=None,maxsplit=-1) // 默认按空格切分,所有都切
"苟日新,日日新,又日新".split(',',1) // 输出['苟日新','日日新,又日新'] 

// 按行分割,结果以列表返回
splitlines(keepends=False) // 参数是用来指定 结果是否要包含换行符,默认不包含
"苟日新\n日日新\r\n又日新".splitlines() // 输出['苟日新','日日新','又日新'] 
"苟日新\n日日新\r\n又日新".splitlines(True) // 输出['苟日新\n','日日新\r\n','又日新'] 

// 拼接
".".join(["www","ilovefishc","com"]) // 'www.ilovefishc.com'

// 格式化字符串
"{1}看到{0}就会很激动!".format("小甲鱼","漂亮的小姐姐") // '漂亮的小姐姐看到小甲鱼就会很激动'
// 参数中的字符串会当做元组的元素来对待,花括号中的正是元素的索引值,同一个索引值可以被引用多次
"{0}{0}{1}{1}".format("是","非") // 是是非非
// 还可以这样写
"我叫{name},我爱{fav}".format(name = "小甲鱼",fav = "Python")
// 要输出花括号,可以用花括号注释他,也可以传参数传入花括号
"{},{},{}".format(1,"{}",2)
"{},{{}},{}".format(1,2)
// '<'强制字符串在可用空间内左对齐(默认)
// '>'强制字符串在可用空间内右对齐
// '='强制将填充放置在符号(如果有)之后但在数字之前的位置(如"+0000120"的形式打印字符串)
// '^'强制字符串在可用空间内居中
"{:^10}".format(250) // '   250    '(前面三个后面四个)
"{1:>10}{0:<10}".format(520,250) // '       250520       ',其中1是位置索引,><是对齐方向,10是显示宽度
"{right:>10}{left:<10}".format(right=520,left=250) // '       520250       '
"{:010}".format(520)  // '0000000520'
// 也可写为
"{:0=10}".format(520)  // '0000000520'
"{:010}".format(-520)  // '-000000520'
"{1:%>10}{0:%<10}".format(520,250) // '%%%%%%%250520%%%%%%%'

// '+' 正数在前面添加正号(+),负数在前面添加负号(-)
// '-' 只有负数在前面添加负号(-),默认行为
// 空格 正数在前面添加一个空格,负数在前面添加负号(-)
"{:+}{:-}".format(520,-250) // '+520,-250'
// 可以使用英文, _作为千位分隔符
"{:,}".format(123456789) // '123,456,789'
"{:_}".format(123456789) // '123_456_789'
// 浮点数保留几位小数的精度
"{:.2f}".format(3.1415)  // '3.14'
"{:.2g}".format(3.1415)  // '3.1' , 限定的是小数点前后总共多少位
// 也可用于非数值类型,如字符串,就是截取前多少位
"{:.6}".format("I love FishC") // 输出 'I love'
// 精度选项不可以应用在整数上
"{:.2}".format(520) // 报错, Precision not allowed in integer format specifier

// 类型选项决定数据应当如何去呈现
// 'b' 将参数以二进制的形式输出
// 'c' 将参数以Unicode字符的形式输出
// 'd' 将参数以十进制的形式输出
// 'o' 将参数以八进制的形式输出
// 'x' 将参数以十六进制的形式输出
// 'X' 将参数以十六进制的形式输出
// 'n' 跟d类似,不同之处在于它会使用当前语言环境设置的分隔符插入到恰当的位置
// None 跟d一样

"{:b}".format(80) // 输出'1010000'
"{:c}".format(80) // 输出'P'
"{:d}".format(80) // 输出'80'
"{:o}".format(80) // 输出'120'
"{:x}".format(80) // 输出'50'
// 加个#号则以二、八十六进制输出可以自动追加前缀
"{:#b}".format(80) // 输出'0b1010000'
"{:#o}".format(80) // 输出'0o120'
"{:#x}".format(80) // 输出'0x50'

// 适用于浮点数和复数的
// 'e' 将参数以科学计数法的形式输出(以字母'e'来标示指数,默认精度为6) 
// 'E' 将参数以科学计数法的形式输出(以字母'E'来标示指数,默认精度为6) 
// 'f' 将参数以定点表示法的形式输出("不是数"用'nan'标示,无穷用'inf'标示,默认精度为6)
// 'F' 将参数以定点表示法的形式输出("不是数"用'NAN'标示,无穷用'INF'标示,默认精度为6)
// 'g' 通用格式,小数以'f'形式输出,大数以'e'的形式输出
// 'G' 通用格式,小数以'F'形式输出,大数以'E'的形式输出
// 'n' 跟'g'类似,不同之处在于它会使用当前语言环境设置的分隔符插入到恰当位置
// '%' 以百分比的形式输出,后面附带一个百分号
// None 类似于'g',不同之处在于当使用定点表示法时,小数点后将至少显示一位,默认精度与给定值所需的精度一致
"{:e}".format(3.1415)  // 输出'3.141500e+00'
"{:f}".format(3.1415)  // 输出'3.141500'
"{:g}".format(123456789) // 输出'1.23457e+08'
"{:g}".format(1234.56789) // 输出'1234.57'
"{:%}".format(0.98)  // 输出'98.000000%'
"{:.2%}".format(0.98) // 输出'98.00%'
"{:.{prec}f}".format(3.1415,prec=2) // 输出'3.14'
"{:{fill}{align}{width}.{prec}{ty}}".format(3.1415,fill='+',align='^',width=10,prec=3,ty='g') //输出'+++3.14+++'
// 语法糖,F字符串,在字符串前加f或者F
year = 2010
f"鱼C工作室成立于{year}年"
F"{-520:010}" // 输出'-000000520'
f"{123456789:,}" // 输出123,456,789

1.4 序列

1.4.1 序列基本操作

// 加号
[1,2,3] + [4,5,6] // [1,2,3,4,5,6]
(1,2,3) + (4,5,6) // (1,2,3,4,5,6)
"123" + "456" // '123456'
// 乘号
[1,2,3] * 3 // [1,2,3,1,2,3,1,2,3]
(1,2,3) * 3 // (1,2,3,1,2,3,1,2,3)
"123" * 3 // '123123123'
// 可变序列,如列表增量赋值,其id不变
s = [1,2,3]
id(s) = 2448604013192
s *= 2
id(s) = 2448604013192
// 但是不可变序列如元组增量赋值,其id则会改变
// 同一性运算符 is 和 is not
// 通过检测两个对象的id判断是不是同一个对象
// 包含运算符in 和 not in 判断序列中是否包含某元素
"C" in "FishC" // True
// 删除运算符 del
x = [1,2,3]
y = "FishC"
del x,y 
x // name 'x' is not defined
y // name 'y' is not defined
// del还可以删除序列中的指定元素
x = [1,2,3,4,5]
del x[1:4]
x // [1,5]
// 也可用切片实现,先将=左边内容清空,在将空列表插入到该位置,等于也实现了删除
x[1:4] = []
// 清空clear
x.clear()
x // []
//相当于
del x[:]

1.4.2 列表、元组和字符串相互转换

//(1)字符串变成列表
list("FishC") // ['F','i','s','h','C']
//(2)元组变成列表
list((1,2,3,4,5)) // [1,2,3,4,5]
//(3)可迭代对象转换为元组
tuple("FishC") // ('F','i','s','h','C')
tuple([1,2,3,4,5]) // (1,2,3,4,5)
//(4)可迭代对象转化为字符串
str([1,2,3,4,5]) // '[1,2,3,4,5]'
str((1,2,3,4,5)) // '(1,2,3,4,5)'
// min()函数可以通过设置default参数避免空序列报错
s = []
min(s, defalult="空序列,找不到") // '空序列,找不到' 
// s.sort()会改变原列表的排序,但是sorted(s)会返回一个船新的列表,原列表不受影响
s = [1,2,3,0,6]
sorted(s, reverse=True) // [6,3,2,1,0] 排序后翻转
// key的使用,key指定一个会干预排序算法的函数
t = ["FishC","Apple","Book","Banana","Pen"]
sorted(t) // ['Apple','Banana','Book','FishC','Pen']
sorted(t, key=len) // ['Pen','Book','FishC','Apple','Banana']
// 也可以写成
t.sort(key=len) // ['Pen','Book','FishC','Apple','Banana']
// sort()只能处理列表,sorted()函数能处理任何形式的可迭代对象作为参数,返回的结果都是列表
sorted("FishC") // ['C','F','h','i','s']
sorted((1,0,0,8,6)) // [0,0,1,6,8]
// reversed(s) 返回的是一个迭代器对象,然后再将其变为列表即可
s = [1,2,5,8,0]
reversed(s) // <list_reverseiterator object at 0x0000023A1C2EACF8>
list(reversed(s)) // [0, 8, 5, 2, 1]
s.reverse() // [0, 8, 5, 2, 1]
list(reversed(range(0,10))) // [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

// all() 和 any()
// all() 判断可迭代对象中是否所有元素都为真
// any() 判断是否存在一个为真
x = [1,1,0]
y = [1,1,9]
all(x) // False
all(y) // True

1.4.3 enumerate()

// enumerate()函数用于返回一个枚举对象,它的功能就是将可迭代对象中的每个元素及从0开始的序号共同构成一个二元组的列表
>>> seasons = ["Spring","Summer","Fall","Winter"]
>>> enumerate(seasons)
<enumerate object at 0x0000023A1C2F5480>
>>> list(enumerate(seasons))
[(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]
// 想让序号从10开始,则可以给enumerate传入参数10
>>> list(enumerate(seasons,10))
[(10, 'Spring'), (11, 'Summer'), (12, 'Fall'), (13, 'Winter')]

// zip()函数用于创建一个聚合多个可迭代对象的迭代器。它会将作为参数传入的可迭代对象的每个元素依次组合成元组,即第i个元组包含来自每个参数的第i个元素

>>> x = [1,2,3]
>>> y = [4,5,6]
>>> zipped = zip(x,y)
>>> list(zipped)
[(1, 4), (2, 5), (3, 6)]
>>> z = [7,8,9]
>>> zipped = zip(x,y,z)
>>> list(zipped)
[(1, 4, 7), (2, 5, 8), (3, 6, 9)]
>>> z = "FishC"
>>> zipped = zip(x,y,z)
>>> list(zipped)
[(1, 4, 'F'), (2, 5, 'i'), (3, 6, 's')]
// 不想丢掉尾巴则可以用下面实现
>>> import itertools
>>> zipped = itertools.zip_longest(x,y,z)
>>> list(zipped)
[(1, 4, 'F'), (2, 5, 'i'), (3, 6, 's'), (None, None, 'h'), (None, None, 'C')]

// map()函数会根据提供的函数对指定的可迭代对象的每个元素进行运算,并将返回运算结果的迭代器
>>> mapped = map(ord,"FishC")
>>> list(mapped)
[70, 105, 115, 104, 67]

>>> mapped = map(pow,[2,3,10],[5,2,3])
>>> list(mapped)
[32, 9, 1000]
// 相当于
[pow(2,5),pow(3,2),pow(10,3)]
>>> list(map(max,[1,3,5],[2,2,2],[0,3,9,8]))
[2, 3, 9] // 剩下的8无人对比就不管了

// filter()函数会根据提供的函数对指定的可迭代对象的每个元素进行运算,并将运算结果为真的元素,以迭代器的形式返回
>>> list(filter(str.islower, "FishC"))
['i', 's', 'h']

// 可迭代对象可以重复使用而迭代器是一次性的
>>> mapped = map(ord,"FishC")
>>> for each in mapped:
	print(each)
70
105
115
104
67
>>> list(mapped)
[]

// 可迭代对象变为一次性的迭代器,iter()函数
>>> x = [1,2,3,4,5]
>>> y = iter(x)
>>> type(x)
<class 'list'>
>>> type(y)
<class 'list_iterator'>

// next()函数,将元素从迭代器中提取出来
>>> next(y)
1
>>> next(y)
2
>>> next(y)
3
>>> next(y)
4
>>> next(y)
5
>>> next(y)
Traceback (most recent call last):
  File "<pyshell#84>", line 1, in <module>
    next(y)
StopIteration

// 让其不报异常,传入参数
>>> z = iter(x)
>>> next(z,"没啦")
1
>>> next(z,"没啦")
2
>>> next(z,"没啦")
3
>>> next(z,"没啦")
4
>>> next(z,"没啦")
5
>>> next(z,"没啦")
'没啦'

1.5 字典

1.5.1 创建字典

>>> a = {"吕布":"口口布","关羽":"关习习","刘备":"刘baby"}
>>> b = dict(吕布="口口布",关羽="关习习",刘备="刘baby") // 这种方式键不能加引号
>>> c = dict([("吕布","口口布"),("关羽","关习习"),("刘备","刘baby")]) // 传入列表,键值对用元组形式传入
>>> d = dict({"吕布":"口口布","关羽":"关习习","刘备":"刘baby"}) // 将第一种传到dict方法中去
>>> e = dict({"吕布":"口口布","关羽":"关习习"},刘备="刘baby") // 混合法
>>> f = dict(zip(["吕布","关羽","刘备"],["口口布","关习习","刘baby"]))
>>> a == b == c == d == e == f
True

1.5.2 新增字典

>>> d = dict.fromkeys("Fish",250)
>>> d
{'F': 250, 'i': 250, 's': 250, 'h': 250}
>>> d['C'] = 67
>>> d
{'F': 250, 'i': 250, 's': 250, 'h': 250, 'C': 67}

1.5.3 修改字典

>>> d["F"] = 70
>>> d
{'F': 70, 'i': 250, 's': 250, 'h': 250}

>>> d = dict.fromkeys("FishC")
>>> d
{'F': None, 'i': None, 's': None, 'h': None, 'C': None}
>>> d.update({'i':105,'h':104})
>>> d
{'F': None, 'i': 105, 's': None, 'h': 104, 'C': None}
>>> d['s'] = 115
>>> d
{'F': None, 'i': 105, 's': 115, 'h': 104, 'C': None}
>>> d.update(F='70',C='67')
>>> d
{'F': '70', 'i': 105, 's': 115, 'h': 104, 'C': '67'}

1.5.4 删除字典

>>> d.pop('s')
250
>>> d.pop('c')
Traceback (most recent call last):
  File "<pyshell#29>", line 1, in <module>
    d.pop('c')
KeyError: 'c' // pop()方法有则删除这个键并返回值,无则抛出异常
>>> d.popitem() // 会弹出最后一个加入的键值对
('C', 67)
>>> del d['i']
>>> d
{'F': 70, 'h': 250}

>>> del d
>>> d
Traceback (most recent call last):
  File "<pyshell#36>", line 1, in <module>
    d
NameError: name 'd' is not defined

d.clear() // 会将字典变成一个空字典 {}

1.5.5 查询字典

>>> d['g'] // 如果没有这个键则会报错,用户体验不是很友好
Traceback (most recent call last):
  File "<pyshell#51>", line 1, in <module>
    d['g']
KeyError: 'g'

// 推荐使用get方法
>>> d.get('认输',"我的字典里没有认输二字")
'我的字典里没有认输二字'

// 查询是否存在该键,存在则返回值,不存在则添加这个键值对并返回值
>>> d.setdefault('C',"code")
'67'
>>> d.setdefault('c',"code")
'code'

1.5.6 keys()、values()、items()方法

>>> keys = d.keys()
>>> values = d.values()
>>> items = d.items()
>>> items
dict_items([('F', '70'), ('i', 105), ('s', 115), ('h', 104), ('C', '67'), ('c', 'code')])
>>> keys
dict_keys(['F', 'i', 's', 'h', 'C', 'c'])
>>> values
dict_values(['70', 105, 115, 104, '67', 'code'])
// 当pop()出一个键值对后,这三个方法返回值也会相应改变

1.5.7 字典的浅拷贝

>>> e = d.copy()

1.5.8 字典的其他操作

>>>'C' in d //True
>>>'C' not in d // False
>>> list(d)
['F', 'i', 's', 'h', 'C', 'c']
>>> list(d.values())
['70', 105, 115, 104, '67', 'code']

>>> e = iter(d)
>>> next(e)
'F'
>>> next(e)
'i'
>>> next(e)
's'
>>> next(e)
'h'
>>> next(e)
'C'
>>> next(e)
'c'
>>> next(e)
Traceback (most recent call last):
  File "<pyshell#92>", line 1, in <module>
    next(e)
StopIteration

>>> list(reversed(d.keys()))
Traceback (most recent call last):
  File "<pyshell#95>", line 1, in <module>
    list(reversed(d.keys()))
TypeError: 'dict_keys' object is not reversible
>>> list(reversed(d.values()))
Traceback (most recent call last):
  File "<pyshell#96>", line 1, in <module>
    list(reversed(d.values()))
TypeError: 'dict_values' object is not reversible // python3.6不可以,python3.9可以

>>> d
{'A': '23', 'B': '99'}
>>> list(reversed(d.keys()))
['B', 'A']
>>> list(reversed(d.values()))
['99', '23']
>>> d
{'A': '23', 'B': '99'}

1.5.9 字典的嵌套

>>> d = {"吕布":{"语文":70,"数学":80,"英语":60},"关羽":{"语文":80,"数学":90,"英语":70}}
>>> d["吕布"]["数学"]
80

// 嵌套也可以嵌套一个序列
>>> d = {"吕布":[70,80,90],"关羽":[50,60,70]}
>>> d["吕布"][1]
80

// 键值的调换
>>> d = {'F':70, 'i':105, 's':115, 'h':104, 'C':67}
>>> b = {v:k for k,v in d.items()}
>>> b
{70: 'F', 105: 'i', 115: 's', 104: 'h', 67: 'C'}

>>> b = {v:k for k,v in d.items() if v > 100}
>>> b
{105: 'i', 115: 's', 104: 'h'}

>>> d = {x:ord(x) for x in "FishC"}
>>> d
{'F': 70, 'i': 105, 's': 115, 'h': 104, 'C': 67}

>>> d = {x:y for x in [1,3,5] for y in [2,4,6]}
>>> d
{1: 6, 3: 6, 5: 6}

1.6 集合

1.6.1 集合的无序性

>>> {s for s in "FishC"}
{'s', 'i', 'F', 'C', 'h'}

// 或者用集合构造器初始化集合
>>> set("FishC")
{'s', 'i', 'F', 'C', 'h'}
// 集合是无序的,所以不能用下标的方式访问它

1.6.2 集合去重

>>> set([1,1,2,3,4])
{1,2,3,4}

1.6.3 集合是否存在交集

>>> s = set("FishC")
>>> s.isdisjoint(set("Python"))
False // 存在交集h

1.6.4 集合是否是子集

// 子集:对于两个集合A、B,如果集合A中任意一个元素都是集合B中的元素,我们就说这两个集合有包含关系,称集合A为集合B的子集
>>> s.issubset("FhisC")
True
>>> s.issubset("FishC.com")
True
// 超集:对于两个集合A、B,如果集合B中任意一个元素都是集合A中的元素,我们就说这两个集合有包含关系,称集合A为集合B的超集
>>> s.issuperset("Fish")
True

1.6.5 并集union

>>> s.union({1,2,3})
{1, 2, 3, 's', 'i', 'F', 'C', 'h'}

1.6.6 交集intersection

>>> s.intersection("Fish")
{'F', 'h', 's', 'i'}

1.6.7 差集difference

>>> s.difference("Fish")
{'C'}

// 以上方法都可以是多参数的
>>> s.union({1,2,3},"Python")
{'y', 1, 2, 3, 'n', 's', 'P', 'i', 'F', 'C', 'o', 'h', 't'}

>>> s.intersection("Php","Python")
{'h'}

>>> s.difference("Php","Python")
{'s', 'i', 'F', 'C'}

1.6.8 对称差集

// 对于两个集合A、B,先排除集合A与集合B的所有共同元素,由剩余的元素组成的集合,叫做集合A与集合B的对称差集,不能多参数
>>> s.symmetric_difference("Python")
{'y', 'n', 'P', 's', 'i', 'F', 'C', 'o', 't'}

1.6.9 运算符对于集合判断是否为子集、真子集的作用

>>> s <= set("FishC")
True

>>> s <= set("FishC")
True
>>> s <= set("Fishc")
False
>>> s < set("Fishc")
False
>>> s < set("FishC")
False
>>> s < set("FishC.com.cn")
True

// 并集 |
>>> s | {1,2,3} | set("Python")
{'y', 1, 2, 3, 'n', 's', 'P', 'i', 'C', 'h', 'F', 'o', 't'}

// 交集 &
>>> s & set("Php") & set("Python")
{'h'}

// 差集 -
>>> s - set("Php") - set("Python")
{'F', 's', 'C', 'i'}

// 对称差集 ^
>>> s ^ set("Python")
{'y', 'n', 'P', 's', 'i', 'F', 'C', 'o', 't'}

// 使用运算符,两边都必须得是集合,不能一边集合一边字符串之类的
>>> s <= "FishC"
Traceback (most recent call last):
  File "<pyshell#165>", line 1, in <module>
    s <= "FishC"
TypeError: '<=' not supported between instances of 'set' and 'str'

1.6.10 不可变集合

>>> t = frozenset("FishC")
>>> t
frozenset({'s', 'i', 'F', 'C', 'h'})
// 不能使用update进行更新

1.6.11 只适用于集合的方法

// update()
>>> s = set("FishC")
>>> s.update([1,1], "23")
>>> s
{1, 's', 'i', 'F', 'C', '3', '2', 'h'}

>>> s.intersection_update("FishC")
>>> s
{'s', 'i', 'F', 'C', 'h'}

>>> s.difference_update("Php","Python")
>>> s
{'s', 'i', 'F', 'C'}

>>> s
{'y', 'n', 's', 'P', 'i', 'F', 'C', 'o', 'h', 't'}

// add
>>> s.add("45")
>>> s
{'y', 'n', 's', 'P', 'i', '45', 'F', 'C', 'o', 'h', 't'} // add传入的字符串是将整个字符串插入到集合中,而update是迭代字符串每一个字符都插入

// remove(),元素不存在会抛出异常、discard()静默处理
Traceback (most recent call last):
  File "<pyshell#195>", line 1, in <module>
    s.remove("w")
KeyError: 'w'

// pop() 随机弹出一个元素
// clear() 清空集合 变成 set()

1.7 哈希

>>> hash(1)
1
>>> hash(1.0)
1
>>> hash(1.0001)
230584300921345
>>> hash("FishC")
-6710854582740544039

>>> hash([1,2,3])
Traceback (most recent call last):
  File "<pyshell#203>", line 1, in <module>
    hash([1,2,3])
TypeError: unhashable type: 'list' // 列表这种随时可变的对象不可哈希
>>> hash((1,2,3))
2528502973977326415
// 只有可哈希的才可以作为字典的键和集合的元素

>>> {[1,2,3]:"FishC"}
Traceback (most recent call last):
  File "<pyshell#209>", line 1, in <module>
    {[1,2,3]:"FishC"}
TypeError: unhashable type: 'list'
>>> {"Python","FishC",520,1314,[1,1,2]}
Traceback (most recent call last):
  File "<pyshell#212>", line 1, in <module>
    {"Python","FishC",520,1314,[1,1,2]}
TypeError: unhashable type: 'list'

// 集合是可变容器,也是不可哈希的
>>> x = {1,2,3}
>>> y = {x,4,5}
Traceback (most recent call last):
  File "<pyshell#218>", line 1, in <module>
    y = {x,4,5}
TypeError: unhashable type: 'set'
// 非要嵌套集合,可以使用不可变集合frozenset
>>> x = frozenset(x)
>>> y = {x,4,5}
>>> y
{5, frozenset({1, 2, 3}), 4}

// 将列表变成集合,效率可以大大提升,因为集合存在散列表,代价是牺牲海量的存储空间,用空间换时间

1.8 函数

1.8.1 函数返回值

>>> def myfunc(name):
	for i in range(3):
		print(f"I love {name}.")

>>> myfunc("Python")
I love Python.
I love Python.
I love Python.

>>> def div(x,y):
	if y==0:
		return "除数不能为0!"
	else:
		return x/y

>>> div(4,0)
'除数不能为0!'

1.8.2 无返回值函数也会返回None

>>> def myfunc():
	pass
>>> print(myfunc())
None

1.8.3 关键字参数

>>> def myfunc(s,vt,o):
	return "".join((o,vt,s))
// 位置参数
>>> myfunc("我","打了","小甲鱼")
'小甲鱼打了我'
// 关键字参数
>>> myfunc(o="我",vt="打了",s="小甲鱼")
'我打了小甲鱼'
// 混用的话位置参数必须在关键字参数之前
// 默认值参数可以写在形参里,不输入的话则用默认值,默认值参数必须定义在最后,可以多个默认值参数,都放在最后就行
// help(abs),发现返回的是abs(x,/),意思是斜杠左边不能使用关键字参数,只能使用位置参数
>>> help(sum)
Help on built-in function sum in module builtins:

sum(iterable, start=0, /)
    Return the sum of a 'start' value (default: 0) plus an iterable of numbers
    
    When the iterable is empty, return the start value.
    This function is intended specifically for use with numeric values and may
    reject non-numeric types.

>>> sum([1,2,3],4)
10
>>> sum([1,2,3],start=4)
Traceback (most recent call last):
  File "<pyshell#265>", line 1, in <module>
    sum([1,2,3],start=4)
TypeError: sum() takes no keyword arguments

>>> def abc(a, /, b, c):
        print(a,b,c)
>>> abc(a=1,2,3)
  File "<stdin>", line 1
    abc(a=1,2,3)
               ^
SyntaxError: positional argument follows keyword argument
>>> abc(1,b=2,c=3)
1 2 3

// 强制右侧使用关键词参数,则用 *
>>> def abc(a,*,b,c):
	print(a,b,c)	
>>> abc(1,2,3)
Traceback (most recent call last):
  File "<pyshell#277>", line 1, in <module>
    abc(1,2,3)
TypeError: abc() takes 1 positional argument but 3 were given
>>> abc(1,c=2,b=3)
1 3 2

1.8.4 收集参数

// 参数不固定
>>> def myfunc(*args):
	print("有{}个参数。".format(len(args)))
	print("第2个参数是{}".format(args[1]))
>>> myfunc("nihao","jiayu")2个参数。
第2个参数是jiayu

1.8.5 返回多个值:用的是元组

>>> def myfunc(*args):
	print(args)	
>>> myfunc(1,2,3,4,5)
(1, 2, 3, 4, 5)

>>> def myfun():
	return 1,2,3
>>> myfun()
(1, 2, 3)
// 星号就是打包参数,也可以解包
>>> x,y,z = myfun()
>>> x
1
>>> y
2
>>> z
3

// 对星号参数进行输出,可以看到类型是元组
>>> def myfunc(*args):
	print(type(args))
>>> myfunc(1,2,3,4,5)
<class 'tuple'>

// 用了*的收集参数,后面出现的参数必须都是关键字参数,不然分配不到实参给后面的
def myfunc(*args,a,b):
	print(args,a,b)
>>> myfunc(1,2,3,a=4,b=5)
(1, 2, 3) 4 5

// 参数还可以打包成字典:用两个**
>>> def myfunc(**kwargs):
	print(kwargs)
>>> myfunc(a=1,b=2,c=3)
{'a': 1, 'b': 2, 'c': 3}

// 混用如下:
>>> def myfunc(a, *b, **c):
	print(a,b,c)
>>> myfunc(1,2,3,4,x=5,y=6)
1 (2, 3, 4) {'x': 5, 'y': 6}


// 星号,解包参数
>>> args = (1,2,3,4)
>>> def myfunc(a,b,c,d):
	print(a,b,c,d)
>>> myfunc(args)
Traceback (most recent call last):
  File "<pyshell#344>", line 1, in <module>
    myfunc(args)
TypeError: myfunc() missing 3 required positional arguments: 'b', 'c', and 'd'
>>> myfunc(*args)
1 2 3 4
>>> kwargs = {'a':1,'b':2,'c':3,'d':4}
>>> myfunc(**kwargs)
1 2 3 4

1.9 作用域

1.9.1 global关键词

// 函数中用global关键词限定,则使用的就是全局的那个变量,不提倡这样做
>>> x = 880
>>> def myfunc():
	global x
	x = 520
	print(x)

>>> myfunc()
520
>>> x
520

1.9.2 函数嵌套

>>> def funA():
	x = 520
	def funB():
		x = 880
		print("In funB, x =",x)
	funB()
	print("In funA, x =",x)

>>> funA() // 嵌套函数无法在外面直接调用它,只能在函数里边调用
In funB, x = 880
In funA, x = 520

// nonlocal关键词,让变量可以在函数内修改外部作用域的变量值
>>> def funA():
	x = 520
	def funB():
		nonlocal x
		x = 880
		print("In funB, x =",x)
	funB()
	print("In funA, x =",x)
	
>>> funA()
In funB, x = 880
In funA, x = 880

// 函数变量作用域的法则:LEGB,local>Enclosed>Global>Build-in
>>> str = "小甲鱼把str给毁了"
>>> str(520)
Traceback (most recent call last):
  File "<pyshell#33>", line 1, in <module>
    str(520)
TypeError: 'str' object is not callable
// 因为Global>build,所以str此时是个string类型的变量名,所以尽量不要取一些和内置函数相同的变量名

// 如何不通过funA调用funB
>>> def funA():
	x = 880
	def funB():
		print(x)
	return funB

>>> funA()
<function funA.<locals>.funB at 0x000001DFA81E1A60>
>>> funA()()
880
>>> funny = funA()
>>> funny()
880
// 对嵌套函数来说,外层函数的作用域在调用完后还会以某种方式保存,外层函数的变量也会,并不像局部作用域那样调用完就消失
>>> def power(exp):
	def exp_of(base):
		return base ** exp
	return exp_of

>>> square = power(2)
>>> cube = power(3)
>>> square(2)
4
>>> cube(2)
8

// 使用nonlocal修改外部函数的变量
>>> def outer():
	x = 0
	y = 0
	def inner(x1,y1):
		nonlocal x,y
		x += x1
		y += y1
		print(f"现在,x = {x}, y = {y}")
	return inner

>>> move = outer()
>>> move(1,2)
现在,x = 1, y = 2
>>> move(-2,2)
现在,x = -1, y = 4

1.9.3 函数闭包(拿函数当参数)

>>> import time
>>> def time_master(func):
	print("开始运行程序...")
	start = time.time()
	func()
	stop = time.time()
	print("结束运行程序...")
	print(f"一共耗费了{(stop-start):.2f}秒")
	
>>> def myfunc():
	time.sleep(2)
	print("Hello Fishc.")

>>> time_master(myfunc)
开始运行程序...
Hello Fishc.
结束运行程序...
一共耗费了2.01

1.9.4 函数装饰器

>>> import time
>>> def time_master(func):
	def call_func():
		print("开始运行程序...")
		start = time.time()
		func()
		stop = time.time()
		print("结束程序运行...")
		print(f"一共耗费了{(stop-start):.2f}秒")
	return call_func

>>> @time_master // 装饰器语法糖,后面调用myfunc并不是直接调用myfunc,而是作为一个参数赛到time_master里边去
def myfunc():
	time.sleep(2)
	print("I love fishC.")

>>> myfunc()
开始运行程序...
I love fishC.
结束程序运行...
一共耗费了2.01// 上面代码没有@time_master也可以,下面写成这样:
myfunc = time_master(myfunc)
myfunc()


// 同时定义多个装饰器,调用顺序是先调用最新的
>>> def add(func):
	def inner():
		x = func()
		return x+1
	return inner
>>> def cube(func):
	def inner():
		x = func()
		return x*x*x
	return inner
>>> def square(func):
	def inner():
		x = func()
		return x*x
	return inner
@add
@cube
@square
def test():
	return 2

>>> print(test())
65

1.9.5 带参数的函数装饰器

>>> import time
>>> def logger(msg):
	def time_master(func):
		def call_func():
			start = time.time()
			func()
			stop = time.time()
			print(f"[{msg}]一共耗费了{(stop-start):.2f}秒")
		return call_func
	return time_master
>>> def funA():
	time.sleep(1)
	print("正在调用funA...")
>>> def funB():
	time.sleep(1)
	print("正在调用funB...")
>>> funA = logger(msg="A")(funA)
>>> funB = logger(msg="B")(funB)
>>> funA()
正在调用funA...
[A]一共耗费了1.04>>> funB()
正在调用funB...
[B]一共耗费了1.02// 上面的代码相当于
@logger(msg="A")
def funA():
	time.sleep(1)
	print("正在调用funA...")
@logger(msg="B")
def funB():
	time.sleep(1)
	print("正在调用funB...")	

1.10 Lambda表达式

lambda arg1, arg2, arg3, … argN : expression
传统形式:
def (arg1, arg2, arg3, … argN):
return expression

lambda表达式可以出现在python中def语句不能出现的地方

// 传统的
>>> def squareX(x):
	return x*x
>>> squareX(3)
9
// lambda表达式
>>> squareY = lambda y:y*y
>>> squareY(3)
9
// 二者都是函数的引用
>>> squareX
<function squareX at 0x000001ED105AE1E0>
>>> squareY
<function <lambda> at 0x000001ED105AE268>

// lambda表达式可应用的地方
>>> mapped = map(lambda x:ord(x)+10,"FishC")
>>> list(mapped)
[80, 115, 125, 114, 77]

// 用lambda表达式返回1-10之间的奇数,filter函数只返回结果为true的
>>> list(filter(lambda x: x%2,range(10)))
[1, 3, 5, 7, 9]

1.11 生成器

每调用一次提供一个数据,生成器是一种特殊的迭代器,支持next()函数。由于生成器是调用一次提供一个数据,这种特性使它不支持下标索引这种随机访问方式

yield关键字,函数调用结束后仍可保留变量

>>> def counter():
	i = 0
	while i <= 5:
		yield i // 每次执行yield i的时候,就生成一个数据,暂停并保留状态,下次调用则从i += 1开始执行
		i += 1
	
>>> counter()
<generator object counter at 0x000001ED105AAFC0>
>>> for i in counter():
	print(i)

0
1
2
3
4
5


// 用迭代器实现斐波那契数列
>>> def fib():
	back1, back2 = 0, 1
	while True:
		yield back1
		back1, back2 = back2, back1 + back2
	
>>> f = fib()
>>> next(f)
0
>>> next(f)
1
>>> next(f)
1
>>> next(f)
2

// 生成器表达式和列表推导式的区别:列表推导式会将所有的数据生成出来并放到一个列表中,而生成器表达式就像一直母鸡,一次只下一个蛋
// 实现生成器的两种方法:
//(1)将普通函数中的return语句替换成yield表达式
//(2)直接使用生成器表达式
>>> (i ** 2 for i in range(10))
<generator object <genexpr> at 0x000001ED105B50A0>
>>> t = (i ** 2 for i in range(10))
>>> next(t)
0
>>> next(t)
1
>>> for i in t:
	print(i)

4
9
16
25
36
49
64
81

1.12 函数文档

// 函数文档一般是出现在函数的最顶端
>>> def exchange(dollar, rate = 6.32):
	"""
	功能:汇率转换,美元 -> 人民币
	参数:
	- dollar 美元数量
	- rate 汇率,默认值是6.322022-03-08)
	返回值:
	- 人民币的数量
	"""
	return dollar * rate

>>> exchange(20)
126.4

// 用help函数可以查看自带的和自定义的函数文档
>>> help(exchange)
Help on function exchange in module __main__:

exchange(dollar, rate=6.32)
    功能:汇率转换,美元 -> 人民币
    参数:
    - dollar 美元数量
    - rate 汇率,默认值是6.322022-03-08)
    返回值:
    - 人民币的数量

1.13 参数类型注释

>>> def times(s:str, n:int) -> str:
	return s * n

>>> times("FishC",5)
'FishCFishCFishCFishCFishC'
>>> times(5,5) // 类型注释只是给机器看的,建议使用者如是传参,但不按建议来也不会报错
25
>>> def times(s:str = "FishC", n:int = 3) -> str: // 可以为其赋予参数默认值
	return s * n

>>> times(3)
9

1.14 内省函数

>>> times.__name__ // 查看函数名
'times'

>>> times.__annotations__ // 查看函数参数类型注释
{'s': <class 'list'>, 'n': <class 'int'>, 'return': <class 'list'>}


>>> exchange.__doc__
'\n\t功能:汇率转换,美元 -> 人民币\n\t参数:\n\t- dollar 美元数量\n\t- rate 汇率,默认值是6.322022-03-08)\n\t返回值:\n\t- 人民币的数量\n\t'
>>> print(exchange.__doc__)

	功能:汇率转换,美元 -> 人民币
	参数:
	- dollar 美元数量
	- rate 汇率,默认值是6.322022-03-08)
	返回值:
	- 人民币的数量

1.15 高阶函数

// reduce()函数
>>> def add(x, y):
	return x + y

>>> import functools
>>> functools.reduce(add,[1,2,3,4,5])
15
>>> add(add(add(add(1,2),3),4),5)
15
>>> functools.reduce(lambda x,y:x+y,range(1,6)) //reduce函数就是将可迭代对象中的元素依次传入到第一个参数指定的函数中,最终返回累积的结果
15

// 偏函数,多个参数多次拆分
>>> import functools
>>> square = functools.partial(pow, exp=2)
>>> square(2)
4     // 类似于函数闭包

// @wraps装饰器
>>> import time
>>> def time_master(func):
	def call_func():
		print("开始运行程序...")
		start = time.time()
		func()
		stop = time.time()
		print("结束程序运行...")
		print(f"一共耗费了{(stop-start):.2f}秒")
	return call_func

>>> @time_master // 装饰器语法糖,后面调用myfunc并不是直接调用myfunc,而是作为一个参数赛到time_master里边去
def myfunc():
	time.sleep(2)
	print("I love fishC.")

>>> myfunc()
开始运行程序...
I love fishC.
结束程序运行...
一共耗费了2.01// 上面代码没有@time_master也可以,下面写成这样:
myfunc = time_master(myfunc)
myfunc()
// 由于装饰器的本质如上,所以它的函数名实质是time_master里调用的call_func函数的函数名
myfunc.__name__
'call_func'

// 加上@wraps后函数名就会变成定义的函数名
>>> import time
>>> import functools
>>> def time_master(func):
	@functools.wraps(func)
	def call_func():
		print("开始运行程序...")
		start = time.time()
		func()
		stop = time.time()
		print("结束程序运行...")
		print(f"一共耗费了{(stop-start):.2f}秒")
	return call_func

def myfunc():
	time.sleep(2)
	print("I love fishC.")

myfunc = time_master(myfunc)
myfunc.__name__
// myfunc的name就变成了myfunc,副作用就没有了
'myfunc'

1.16 永久存储

// 打开文件
>>> f = open("D:\\1.txt","w")
>>> f.write("I love Python.")
14  // 返回的是写入的字符数(字符串的长度)

>>> f.writelines(["I love FishC.\n","I love my wife."])  // 此时还没有写入到文件中,还在这个文件的缓冲区中
>>> f.close()  // 文件关闭这一步相当于按下了保存,才会写入到文件中去

>>> f = open("D:\\1.txt","r+")  // r+相当于更新,既可以读取也可以写入
>>> f.readable()
True  // 测试可读性
>>> f.writable()
True  // 测试可写性

>>> for each in f:
	print(each)
	
I love Python.I love FishC.
I love my wife.  //Python的文件是支持迭代的

>>> f.read()
''   // 因为刚才的for语句已经将文件指针置于文件的末尾,所以读出来是空
>>> f.tell()
44   // tell方法追踪文件指针的位置

>>> f.seek(0)  // 将文件指针重置于文件开头
0   

>>> f.readline()  // 读一行,/n换行符也会读到
'I love Python.I love FishC.\n'
>>> f.read()  // 读到eof的位置,也就是文件末尾
'I love my wife.'
>>> f.write("I love my WIFI")
14
>>> f.flush()  // 将文件对象中的缓存数据写入到文件中(不一定有效),在不用close的情况下也可以保存到硬盘
>>> f.truncate(29)  // 将文件对象截取到pos参数的位置,默认是截取到文件指针当前指定的位置
29
>>> f.close() // 结果是文件变成了I love Python.I love FishC.

// w 写入模式打开什么也不做,然后关闭也会产生截断,会导致文件里的内容不见了
>>> f = open("D:\\1.txt","w")
>>> f.close()

2 小技巧

2.1 range()函数

2022/09/19 10:39

在不同方面 range() 函数返回的对象表现为它是一个列表,但事实上它并不是。当你迭代它时,它是一个能够像期望的序列返回连续项的对象;但为了节省空间,它并不真正构造列表。

我们称此类对象是 可迭代的,即适合作为那些期望从某些东西中获得连续项直到结束的函数或结构的一个目标(参数)。我们已经见过的 for 语句就是这样一个迭代器。list() 函数是另外一个( 迭代器 ),它从可迭代(对象)中创建列表:

>>>list(range(5))
[0,1,2,3,4]

>>>list(range(0,10,3))
[0,3,6,9]

2.2 for循环中的else语句

>>> for n in range(2, 10):
...     for x in range(2, n):
...         if n % x == 0:
...             print(n, 'equals', x, '*', n//x)
...             break
...     else:
...         # loop fell through without finding a factor
...         print(n, 'is a prime number')
...
2 is a prime number
3 is a prime number
4 equals 2 * 2
5 is a prime number
6 equals 2 * 3
7 is a prime number
8 equals 2 * 4
9 equals 3 * 3

(Yes, 这是正确的代码。看仔细:else 语句是属于 for 循环之中, 不是 if 语句。)与循环一起使用时,else 子句与 try 语句的 else 子句比与 if 语句的具有更多的共同点:try 语句的 else 子句在未出现异常时运行,循环的 else 子句在未出现 break 时运行。

2.3 python同时给多个变量赋值

a = 3
a, b = 1, a

如果按照正常的思维逻辑,先进行a = 1,在进行b = a,最后b等于1,但是这里b其实等于3,因为在连续赋值语句中等式右边其实都是局部变量,而不是真正的变量值本身,比如,上面例子中右边的a,在python解析的时候,只是把变量a的指向的变量3赋给b,而不是a=1之后a的结果

2.4 模块

2.4.1 标准模块

变量 sys.path 是解释器模块搜索路径的字符串列表。它由环境变量 PYTHONPATH 初始化,如果没有设定 PYTHONPATH ,就由内置的默认值初始化。你可以用标准的字符串操作修改它:

import sys
sys.path.append('/ufs/guido/lib/python')
2.4.2 导入模块

需要注意的是使用 from package import item 方式导入包时,这个子项(item)既可以是包中的一个子模块(或一个子包),也可以是包中定义的其它命名,像函数、类或变量。import 语句首先核对是否包中有这个子项,如果没有,它假定这是一个模块,并尝试加载它。如果没有找到它,会引发一个 ImportError 异常。

相反,使用类似 import item.subitem.subsubitem 这样的语法时,这些子项必须是包,最后的子项可以是包或模块,但不能是前面子项中定义的类、函数或变量。

2.4.3 显式绝对导入
from sound.Effects import echo
2.4.4 隐式相对导入
from . import echo
from .. import formats
from ..filters import equalizer

需要注意的是显式或隐式相对位置导入都基于当前模块的命名。因为主模块的名字总是 “main”,Python 应用程序的主模块应该总是用绝对导入。

2.5 输入和输出

函数 str() 用于将值转化为适于人阅读的形式,而 repr() 转化为供解释器读取的形式(如果没有等价的语法,则会发生 SyntaxError 异常)某对象没有适于人阅读的解释形式的话,str() 会返回与 repr() 等同的值。很多类型,诸如数值或链表、字典这样的结构,针对各函数都有着统一的解读方式。字符串和浮点数,有着独特的解读方式。

>>> s = 'Hello, world.'
>>> str(s)
'Hello, world.'
>>> repr(s)
"'Hello, world.'"
>>> str(1/7)
'0.14285714285714285'
>>> x = 10 * 3.25
>>> y = 200 * 200
>>> s = 'The value of x is ' + repr(x) + ', and y is ' + repr(y) + '...'
>>> print(s)
The value of x is 32.5, and y is 40000...
>>> # The repr() of a string adds string quotes and backslashes:
... hello = 'hello, world\n'
>>> hellos = repr(hello)
>>> print(hellos)
'hello, world\n'
>>> # The argument to repr() may be any Python object:
... repr((x, y, ('spam', 'eggs')))
"(32.5, 40000, ('spam', 'eggs'))"

‘!a’ (应用 ascii()),‘!s’ (应用 str() )和 ‘!r’ (应用 repr() )可以在格式化之前转换值:

>>> import math
>>> print('The value of PI is approximately {}.'.format(math.pi))
The value of PI is approximately 3.14159265359.
>>> print('The value of PI is approximately {!r}.'.format(math.pi))
The value of PI is approximately 3.141592653589793.

2.6 文件读写

f = open('filepath','mode')

第一个参数是一个含有文件名的字符串。第二个参数也是一个字符串,含有描述如何使用该文件的几个字符。mode 为 ‘r’ 时表示只是读取文件;‘w’ 表示只是写入文件(已经存在的同名文件将被删掉);‘a’ 表示打开文件进行追加,写入到文件中的任何数据将自动添加到末尾。 ‘r+’ 表示打开文件进行读取和写入。mode 参数是可选的,默认为 ‘r’。

在文本模式下,读取时默认会将平台有关的行结束符(Unix上是 \n , Windows上是 \r\n)转换为 \n。在文本模式下写入时,默认会将出现的 \n 转换成平台有关的行结束符。这种暗地里的修改对 ASCII 文本文件没有问题,但会损坏 JPEG 或 EXE 这样的二进制文件中的数据。使用二进制模式读写此类文件时要特别小心。

2.6.1 文件对象方法

要读取文件内容,需要调用 f.read(size),该方法读取若干数量的数据并以字符串形式返回其内容,size 是可选的数值,指定字符串长度。如果没有指定 size 或者指定为负数,就会读取并返回整个文件。当文件大小为当前机器内存两倍时,就会产生问题。反之,会尽可能按比较大的 size 读取和返回数据。如果到了文件末尾,f.read() 会返回一个空字符串(‘’):

>>> f.read()
'This is the entire file.\n'
>>> f.read()
''

f.readline() 从文件中读取单独一行,字符串结尾会自动加上一个换行符( \n ),只有当文件最后一行没有以换行符结尾时,这一操作才会被忽略。这样返回值就不会有混淆,如果 f.readline() 返回一个空字符串,那就表示到达了文件末尾,如果是一个空行,就会描述为 ‘\n’,一个只包含换行符的字符串:

>>> f.readline()
'This is the first line of the file.\n'
>>> f.readline()
'Second line of the file\n'
>>> f.readline()
''

你可以循环遍历文件对象来读取文件中的每一行。这是一种内存高效、快速,并且代码简介的方式:

>>> for line in f:
...     print(line, end='')
...
This is the first line of the file.
Second line of the file

用关键字 with 处理文件对象是个好习惯。它的先进之处在于文件用完后会自动关闭,就算发生异常也没关系。它是 try-finally 块的简写:

>>> with open('workfile', 'r') as f:
...     read_data = f.read()
>>> f.closed
True
2.6.2 使用json存储结构化数据

标准模块 json 可以接受 Python 数据结构,并将它们转换为字符串表示形式;此过程称为 序列化。从字符串表示形式重新构建数据结构称为 反序列化。序列化和反序列化的过程中,表示该对象的字符串可以存储在文件或数据中,也可以通过网络连接传送给远程的机器。

如果你有一个对象 x,你可以用简单的一行代码查看其 JSON 字符串表示形式:

>>> json.dumps([1, 'simple', 'list'])
'[1, "simple", "list"]'

dumps() 函数的另外一个变体 dump(),直接将对象序列化到一个文件。所以如果 f 是为写入而打开的一个 文件对象,我们可以这样做:

json.dump(x, f)

为了重新解码对象,如果f是为读取而打开的文件对象(注意filemode必须是r):

x = json.load(f)

2.7 异常处理

通过编程处理选择的异常是可行的。看一下下面的例子:它会一直要求用户输入,直到输入一个合法的整数为止,但允许用户中断这个程序(使用 Control-C 或系统支持的任何方法)。注意:用户产生的中断会引发一个 KeyboardInterrupt 异常。

>>> while True:
...     try:
...         x = int(input("Please enter a number: "))
...         break
...     except ValueError:
...         print("Oops!  That was no valid number.  Try again...")
...

try 语句按如下方式工作。首先,执行try子句(在try和 except 关键字之间的部分)。如果没有异常发生, except 子句 在 try 语句执行完毕后就被忽略了。如果在 try 子句执行过程中发生了异常,那么该子句其余的部分就会被忽略。如果异常匹配于 except 关键字后面指定的异常类型,就执行对应的except子句。然后继续执行 try 语句之后的代码。如果发生了一个异常,在 except 子句中没有与之匹配的分支,它就会传递到上一级 try 语句中。如果最终仍找不到对应的处理语句,它就成为一个 未处理异常,终止程序运行,显示提示信息。

一个 try 语句可能包含多个 except 子句,分别指定处理不同的异常。至多只会有一个分支被执行。异常处理程序只会处理对应的 try 子句中发生的异常,在同一个 try 语句中,其他子句中发生的异常则不作处理。一个 except 子句可以在括号中列出多个异常的名字,例如:

... except (RuntimeError, TypeError, NameError):
...     pass

最后一个 except 子句可以省略异常名称,以作为通配符使用。你需要慎用此法,因为它会轻易隐藏一个实际的程序错误!可以使用这种方法打印一条错误信息,然后重新抛出异常(允许调用者处理这个异常):

import sys

try:
    f = open('myfile.txt')
    s = f.readline()
    i = int(s.strip())
except OSError as err:
    print("OS error: {0}".format(err))
except ValueError:
    print("Could not convert data to an integer.")
except:
    print("Unexpected error:", sys.exc_info()[0])
    raise

在异常名(列表)之后,也可以为 except 子句指定一个变量。这个变量绑定于一个异常实例,它存储在 instance.args 的参数中。为了方便起见,异常实例定义了 str() ,这样就可以直接访问过打印参数而不必引用 .args。这种做法不受鼓励。相反,更好的做法是给异常传递一个参数(如果要传递多个参数,可以传递一个元组),把它绑定到 message 属性。一旦异常发生,它会在抛出前绑定所有指定的属性。

>>> try:
...    raise Exception('spam', 'eggs')
... except Exception as inst:
...    print(type(inst))    # the exception instance
...    print(inst.args)     # arguments stored in .args
...    print(inst)          # __str__ allows args to be printed directly,
...                         # but may be overridden in exception subclasses
...    x, y = inst.args     # unpack args
...    print('x =', x)
...    print('y =', y)
...
<class 'Exception'>
('spam', 'eggs')
('spam', 'eggs')
x = spam
y = eggs

2.7.1 抛出异常

raise 语句允许程序员强制抛出一个指定的异常。例如:

>>> raise NameError('HiThere')
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
NameError: HiThere

要抛出的异常由 raise 的唯一参数标识。它必需是一个异常实例或异常类(继承自 Exception 的类)。
如果你需要明确一个异常是否抛出,但不想处理它,raise 语句可以让你很简单的重新抛出该异常:

>>> try:
...     raise NameError('HiThere')
... except NameError:
...     print('An exception flew by!')
...     raise
...
An exception flew by!
Traceback (most recent call last):
  File "<stdin>", line 2, in ?
NameError: HiThere

2.7.2 用户自定义异常

在程序中可以通过创建新的异常类型来命名自己的异常(Python 类的内容请参见 类 )。异常类通常应该直接或间接的从 Exception 类派生,例如:

>>> class MyError(Exception):
...     def __init__(self, value):
...         self.value = value
...     def __str__(self):
...         return repr(self.value)
...
>>> try:
...     raise MyError(2*2)
... except MyError as e:
...     print('My exception occurred, value:', e.value)
...
My exception occurred, value: 4
>>> raise MyError('oops!')
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
__main__.MyError: 'oops!'

在这个例子中,Exception 默认的 init() 被覆盖。新的方式简单的创建 value 属性。这就替换了原来创建 args 属性的方式。

异常类中可以定义任何其它类中可以定义的东西,但是通常为了保持简单,只在其中加入几个属性信息,以供异常处理句柄提取。如果一个新创建的模块中需要抛出几种不同的错误时,一个通常的作法是为该模块定义一个异常基类,然后针对不同的错误类型派生出对应的异常子类:

class Error(Exception):
    """Base class for exceptions in this module."""
    pass

class InputError(Error):
    """Exception raised for errors in the input.

    Attributes:
        expression -- input expression in which the error occurred
        message -- explanation of the error
    """

    def __init__(self, expression, message):
        self.expression = expression
        self.message = message

class TransitionError(Error):
    """Raised when an operation attempts a state transition that's not
    allowed.

    Attributes:
        previous -- state at beginning of transition
        next -- attempted new state
        message -- explanation of why the specific transition is not allowed
    """

    def __init__(self, previous, next, message):
        self.previous = previous
        self.next = next
        self.message = message

2.7.3 定义清理行为

不管有没有发生异常,finally子句 在程序离开 try 后都一定会被执行。当 try 语句中发生了未被 except 捕获的异常(或者它发生在 except 或 else 子句中),在 finally 子句执行完后它会被重新抛出。 try 语句经由 break ,continue 或 return 语句退 出也一样会执行 finally 子句。以下是一个更复杂些的例子:

>>> def divide(x, y):
...     try:
...         result = x / y
...     except ZeroDivisionError:
...         print("division by zero!")
...     else:
...         print("result is", result)
...     finally:
...         print("executing finally clause")
...
>>> divide(2, 1)
result is 2
executing finally clause
>>> divide(2, 0)
division by zero!
executing finally clause
>>> divide("2", "1")
executing finally clause
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "<stdin>", line 3, in divide
TypeError: unsupported operand type(s) for /: 'str' and 'str'

如你所见, finally 子句在任何情况下都会执行。TypeError 在两个字符串相除的时候抛出,未被 except 子句捕获,因此在 finally 子句执行完毕后重新抛出。

在真实场景的应用程序中,finally 子句用于释放外部资源(文件 或网络连接之类的),无论它们的使用过程中是否出错。

预定义清理行为

有些对象定义了标准的清理行为,无论对象操作是否成功,不再需要该对象的时候就会起作用。以下示例尝试打开文件并把内容打印到屏幕上。

for line in open("myfile.txt"):
    print(line)

这段代码的问题在于在代码执行完后没有立即关闭打开的文件。这在简单的脚本里没什么,但是大型应用程序就会出问题。with 语句使得文件之类的对象可以 确保总能及时准确地进行清理。

with open("myfile.txt") as f:
    for line in f:
        print(line)

语句执行后,文件 f 总会被关闭,即使是在处理文件中的数据时出错也一样。其它对象是否提供了预定义的清理行为要查看它们的文档。

3 类

3.1 术语相关

类继承机制允许多重继承,派生类可以覆盖(override)基类中的任何方法或类,可以使用相同的方法名称调用基类的方法。对象可以包含任意数量的私有数据。

用 C++ 术语来讲,所有的类成员(包括数据成员)都是公有( public )的(其它情况见下文 私有变量),所有的成员函数都是虚( virtual )的。

对象具有特性,并且多个名称(在多个作用域中)可以绑定在同一个对象上。在其它语言中被称为别名。

这通常有助于程序的优化,因为在某些方面别名表现的就像是指针。例如,你可以轻易的传递一个对象,因为通过继承只是传递一个指针。并且如果一个方法修改了一个作为参数传递的对象,调用者可以接收这一变化——这消除了两种不同的参数传递机制的需要,像 Pascal 语言。

3.2 命名空间和作用域

命名空间 是从命名到对象的映射。当前命名空间主要是通过 Python 字典实现的,不过通常不关心具体的实现方式(除非出于性能考虑),以后也有可能会改变其实现方式。以下有一些命名空间的例子:内置命名(像 abs() 这样的函数,以及内置异常名)集,模块中的全局命名,函数调用中的局部命名。某种意义上讲对象的属性集也是一个命名空间。关于命名空间需要了解的一件很重要的事就是不同命名空间中的命名没有任何联系,例如两个不同的模块可能都会定义一个名为 maximize 的函数而不会发生混淆-用户必须以模块名为前缀来引用它们。

顺便提一句,我称 Python 中任何一个“.”之后的命名为 属性 --例如,表达式 z.real 中的 real 是对象 z 的一个属性。严格来讲,从模块中引用命名是引用属性:表达式 modname.funcname 中,modname 是一个模块对象,funcname 是它的一个属性。因此,模块的属性和模块中的全局命名有直接的映射关系:它们共享同一命名空间!

属性可以是只读过或写的。后一种情况下,可以对属性赋值。你可以这样作: modname.the_answer = 42 。可写的属性也可以用 del 语句删除。例如: del modname.the_answer 会从 modname 对象中删除 the_answer 属性。

不同的命名空间在不同的时刻创建,有不同的生存期。包含内置命名的命名空间在 Python 解释器启动时创建,会一直保留,不被删除。模块的全局命名空间在模块定义被读入时创建,通常,模块命名空间也会一直保存到解释器退出。由解释器在最高层调用执行的语句,不管它是从脚本文件中读入还是来自交互式输入,都是 main 模块的一部分,所以它们也拥有自己的命名空间(内置命名也同样被包含在一个模块中,它被称作 builtins )。

当调用函数时,就会为它创建一个局部命名空间,并且在函数返回或抛出一个并没有在函数内部处理的异常时被删除。(实际上,用遗忘来形容到底发生了什么更为贴切。)当然,每个递归调用都有自己的局部命名空间。

作用域 就是一个 Python 程序可以直接访问命名空间的正文区域。这里的直接访问意思是一个对名称的错误引用会尝试在命名空间内查找。尽管作用域是静态定义,在使用时他们都是动态的。每次执行时,至少有三个命名空间可以直接访问的作用域嵌套在一起:

(1)包含局部命名的使用域在最里面,首先被搜索;其次搜索的是中层的作用域,这里包含了同级的函数;最后搜索最外面的作用域,它包含内置命名。

(2)首先搜索最内层的作用域,它包含局部命名任意函数包含的作用域,是内层嵌套作用域搜索起点,包含非局部,但是也非全局的命名

(3)接下来的作用域包含当前模块的全局命名

(4)最外层的作用域(最后搜索)是包含内置命名的命名空间

如果一个命名声明为全局的,那么对它的所有引用和赋值会直接搜索包含这个模块全局命名的作用域。如果要重新绑定最里层作用域之外的变量,可以使用 nonlocal 语句;如果不声明为 nonlocal,这些变量将是只读的(对这样的变量赋值会在最里面的作用域创建一个新的局部变量,外部具有相同命名的那个变量不会改变)。

重要的是作用域决定于源程序的意义:一个定义于某模块中的函数的全局作用域是该模块的命名空间,而不是该函数的别名被定义或调用的位置,了解这一点非常重要

Python 的一个特别之处在于:如果没有使用 global 语法,其赋值操作总是在最里层的作用域。赋值不会复制数据,只是将命名绑定到对象。删除也是如此:del x 只是从局部作用域的命名空间中删除命名 x 。事实上,所有引入新命名的操作都作用于局部作用域。特别是 import 语句和函数定义将模块名或函数绑定于局部作用域(可以使用 global 语句将变量引入到全局作用域)。

global 语句用以指明某个特定的变量为全局作用域,并重新绑定它。nonlocal 语句用以指明某个特定的变量为封闭作用域,并重新绑定它。以下是一个示例,演示了如何引用不同作用域和命名空间,以及 global 和 nonlocal 如何影响变量绑定:

def scope_test():
    def do_local():
        spam = "local spam"
    def do_nonlocal():
        nonlocal spam
        spam = "nonlocal spam"
    def do_global():
        global spam
        spam = "global spam"
    spam = "test spam"
    do_local()
    print("After local assignment:", spam)
    do_nonlocal()
    print("After nonlocal assignment:", spam)
    do_global()
    print("After global assignment:", spam)

scope_test()
print("In global scope:", spam)

以上示例代码的输出为:

After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam

注意:local 赋值语句是无法改变 scope_test 的 spam 绑定。nonlocal 赋值语句改变了 scope_test 的 spam 绑定,并且 global 赋值语句从模块级改变了 spam 绑定。你也可以看到在 global 赋值语句之前对 spam 是没有预先绑定的。

3.3 类对象

进入类定义部分后,会创建出一个新的命名空间,作为局部作用域。因此,所有的赋值成为这个新命名空间的局部变量。特别是函数定义在此绑定了新的命名。类定义完成时(正常退出),就创建了一个 类对象。基本上它是对类定义创建的命名空间进行了一个包装;我们在下一节进一步学习类对象的知识。原始的局部作用域(类定义引入之前生效的那个)得到恢复,类对象在这里绑定到类定义头部的类名(例子中是 ClassName )。

类对象支持两种操作:属性引用和实例化。类对象创建后,类命名空间中所有的命名都是有效属性名。所以如果类定义是这样:

class MyClass:
    """A simple example class"""
    i = 12345
    def f(self):
        return 'hello world'

那么 MyClass.i 和 MyClass.f 是有效的属性引用,分别返回一个整数和一个方法对象。也可以对类属性赋值,你可以通过给 MyClass.i 赋值来修改它。 doc 也是一个有效的属性,返回类的文档字符串:“A simple example class”。

类的 实例化 使用函数符号。只要将类对象看作是一个返回新的类实例的无参数函数即可。例如(假设沿用前面的类):

x = MyClass()

以上创建了一个新的类 实例 并将该对象赋给局部变量 x。这个实例化操作(“调用”一个类对象)来创建一个空的对象。很多类都倾向于将对象创建为有初始状态的。因此类可能会定义一个名为 init() 的特殊方法,像下面这样

def __init__(self):
    self.data = []

类定义了 init() 方法的话,类的实例化操作会自动为新创建的类实例调用 init() 方法。当然,出于弹性的需要,init() 方法可以有参数。事实上,参数通过 init() 传递到类的实例化操作上。例如:

>>> class Complex:
...     def __init__(self, realpart, imagpart):
...         self.r = realpart
...         self.i = imagpart
...
>>> x = Complex(3.0, -4.5)
>>> x.r, x.i
(3.0, -4.5)

3.4 实例对象

实例对象唯一可用的操作就是属性引用

(1)数据属性 相当于 Smalltalk 中的“实例变量”或 C++ 中的“数据成员”。和局部变量一样,数据属性不需要声明,第一次使用时它们就会生成。例如,如果 x 是前面创建的 MyClass 实例,下面这段代码会打印出 16 而在堆栈中留下多余的东西:

x.counter = 1
while x.counter < 10:
    x.counter = x.counter * 2
print(x.counter)
del x.counter

(2)另一种为实例对象所接受的引用属性是 方法。方法是“属于”一个对象的函数。(在 Python 中,方法不止是类实例所独有:其它类型的对象也可有方法。例如,链表对象有 append,insert,remove,sort 等等方法。然而,在后面的介绍中,除非特别说明,我们提到的方法特指类方法)

实例对象的有效名称依赖于它的类。按照定义,类中所有(用户定义)的函数对象对应它的实例中的方法。所以在我们的例子中,x.f 是一个有效的方法引用,因为 MyClass.f 是一个函数。但 x.i 不是,因为 MyClass.i 不是函数。不过 x.f 和 MyClass.f 不同,它是一个 方法对象 ,不是一个函数对象。

3.5 方法对象

通常,方法通过右绑定方式调用:

x.f()

在 MyClass 示例中,这会返回字符串 ‘hello world’。然而,也不是一定要直接调用方法。 x.f 是一个方法对象,它可以存储起来以后调用。例如:

xf = x.f
while True:
    print(xf())

调用 x.f() 时没有引用前面标出的变量,尽管在 f() 的函数定义中指明了一个参数。这个参数怎么了?事实上如果函数调用中缺少参数,Python 会抛出异常--甚至这个参数实际上没什么用……

实际上,你可能已经猜到了答案:方法的特别之处在于实例对象作为函数的第一个参数传给了函数。在我们的例子中,调用 x.f() 相当于 MyClass.f(x) 。通常,以 n 个参数的列表去调用一个方法就相当于将方法的对象插入到参数列表的最前面后,以这个列表去调用相应的函数。

如果你还是不理解方法的工作原理,了解一下它的实现也许有帮助。引用非数据属性的实例属性时,会搜索它的类。如果这个命名确认为一个有效的函数对象类属性,就会将实例对象和函数对象封装进一个抽象对象:这就是方法对象。以一个参数列表调用方法对象时,它被重新拆封,用实例对象和原始的参数列表构造一个新的参数列表,然后函数对象调用这个新的参数列表。

3.6 类和实例变量

一般来说,实例变量用于对每一个实例都是唯一的数据,类变量用于类的所有实例共享的属性和方法,可变对象,例如列表和字典的共享数据可能带来意外的效果。例如,下面代码中的 tricks 列表不应该用作类变量,因为所有的 Dog 实例将共享同一个列表:

class Dog:

    tricks = []             # mistaken use of a class variable

    def __init__(self, name):
        self.name = name

    def add_trick(self, trick):
        self.tricks.append(trick)

>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks                # unexpectedly shared by all dogs
['roll over', 'play dead']

这个类的正确设计应该使用一个实例变量:

class Dog:

    def __init__(self, name):
        self.name = name
        self.tricks = []    # creates a new empty list for each dog

    def add_trick(self, trick):
        self.tricks.append(trick)

>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks
['roll over']
>>> e.tricks
['play dead']

一般,方法的第一个参数被命名为 self。这仅仅是一个约定:对 Python 而言,名称 self 绝对没有任何特殊含义。(但是请注意:如果不遵循这个约定,对其他的 Python 程序员而言你的代码可读性就会变差,而且有些 类查看器 程序也可能是遵循此约定编写的。)

类属性的任何函数对象都为那个类的实例定义了一个方法。函数定义代码不一定非得定义在类中:也可以将一个函数对象赋值给类中的一个局部变量。例如:

# Function defined outside the class
def f1(self, x, y):
    return min(x, x+y)

class C:
    f = f1
    def g(self):
        return 'hello world'
    h = g

现在 f, g 和 h 都是类 C 的属性,引用的都是函数对象,因此它们都是 C 实例的方法-- h 严格等于 g 。要注意的是这种习惯通常只会迷惑程序的读者。通过 self 参数的方法属性,方法可以调用其它的方法:

class Bag:
    def __init__(self):
        self.data = []
    def add(self, x):
        self.data.append(x)
    def addtwice(self, x):
        self.add(x)
        self.add(x)

3.7 继承

命名 BaseClassName (示例中的基类名)必须与派生类定义在一个作用域内。除了类,还可以用表达式,基类定义在另一个模块中时这一点非常有用:

class DerivedClassName(modname.BaseClassName):

派生类可能会覆盖其基类的方法。因为方法调用同一个对象中的其它方法时没有特权,基类的方法调用同一个基类的方法时,可能实际上最终调用了派生类中的覆盖方法。(对于 C++ 程序员来说,Python 中的所有方法本质上都是 虚 方法。)

派生类中的覆盖方法可能是想要扩充而不是简单的替代基类中的重名方法。有一个简单的方法可以直接调用基类方法,只要调用: BaseClassName.methodname(self, arguments)。有时这对于客户也很有用。(要注意只有 BaseClassName 在同一全局作用域定义或导入时才能这样用。)

3.8 私有变量

只能从对像内部访问的“私有”实例变量,在 Python 中不存在。然而,也有一个变通的访问用于大多数 Python 代码:以一个下划线开头的命名(例如 _spam )会被处理为 API 的非公开部分(无论它是一个函数、方法或数据成员)。它会被视为一个实现细节,无需公开。

因为有一个正当的类私有成员用途(即避免子类里定义的命名与之冲突),Python 提供了对这种结构的有限支持,称为 name mangling (命名编码) 。任何形如 __spam 的标识(前面至少两个下划线,后面至多一个),被替代为 _classname__spam ,去掉前导下划线的 classname 即当前的类名。此语法不关注标识的位置,只要求在类定义内。

名称重整是有助于子类重写方法,而不会打破组内的方法调用。例如:

class Mapping:
    def __init__(self, iterable):
        self.items_list = []
        self.__update(iterable)

    def update(self, iterable):
        for item in iterable:
            self.items_list.append(item)

    __update = update   # private copy of original update() method

class MappingSubclass(Mapping):

    def update(self, keys, values):
        # provides new signature for update()
        # but does not break __init__()
        for item in zip(keys, values):
            self.items_list.append(item)

3.9 补充

有时类似于 Pascal 中“记录(record)”或 C 中“结构(struct)”的数据类型很有用,它将一组已命名的数据项绑定在一起。一个空的类定义可以很好的实现它:

class Employee:
    pass

john = Employee() # Create an empty employee record

# Fill the fields of the record
john.name = 'John Doe'
john.dept = 'computer lab'
john.salary = 1000

某一段 Python 代码需要一个特殊的抽象数据结构的话,通常可以传入一个类,事实上这模仿了该类的方法。例如,如果你有一个用于从文件对象中格式化数据的函数,你可以定义一个带有 read() 和 readline() 方法的类,以此从字符串缓冲读取数据,然后将该类的对象作为参数传入前述的函数。

实例方法对象也有属性:m.self 是一个实例方法所属的对象,而 m.func 是这个方法对应的函数对象。

3.10 异常也是类

以下是两种新的,有效的(语义上的)异常抛出形式,使用raise语句:

raise Class

raise Instance

第一种形式中,Class 必须是 type 或其派生类的一个实例。第二种形式是以下形式的简写:

raise Class()

发生的异常其类型如果是 except 子句中列出的类,或者是其派生类,那么它们就是相符的(反过来说--发生的异常其类型如果是异常子句中列出的类的基类,它们就不相符)。例如,以下代码会按顺序打印 B,C,D:

class B(Exception):
    pass
class C(B):
    pass
class D(C):
    pass

for cls in [B, C, D]:
    try:
        raise cls()
    except D:
        print("D")
    except C:
        print("C")
    except B:
        print("B")

要注意的是如果异常子句的顺序颠倒过来( execpt B 在最前),它就会打印 B,B,B--第一个匹配的异常被触发。打印一个异常类的错误信息时,先打印类名,然后是一个空格、一个冒号,然后是用内置函数 str() 将类转换得到的完整字符串。

3.11 迭代器

迭代器的用法在 Python 中普遍而且统一。在后台,for语句在容器对象中调用 iter() 。该函数返回一个定义了 next() 方法的迭代器对象,它在容器中逐一访问元素。没有后续的元素时, next() 抛出一个 StopIteration 异常通知 for 语句循环结束。你可以是用内建的 next() 函数调用 next() 方法;以下是其工作原理的示例:

>>> s = 'abc'
>>> it = iter(s)
>>> it
<iterator object at 0x00A1DB50>
>>> next(it)
'a'
>>> next(it)
'b'
>>> next(it)
'c'
>>> next(it)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
    next(it)
StopIteration

了解了迭代器协议的后台机制,就可以很容易的给自己的类添加迭代器行为。定义一个 iter() 方法,使其返回一个带有 next() 方法的对象。如果这个类已经定义了 next() ,那么 iter() 只需要返回 self:

class Reverse:
    """Iterator for looping over a sequence backwards."""
    def __init__(self, data):
        self.data = data
        self.index = len(data)
    def __iter__(self):
        return self
    def __next__(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]
>>> rev = Reverse('spam')
>>> iter(rev)
<__main__.Reverse object at 0x00A1DB50>
>>> for char in rev:
...     print(char)
...
m
a
p
s

3.12 生成器

Generator 是创建迭代器的简单而强大的工具。它们写起来就像是正规的函数,需要返回数据的时候使用 yield 语句。每次 next() 被调用时,生成器回复它脱离的位置(它记忆语句最后一次执行的位置和所有的数据值)。以下示例演示了生成器可以很简单的创建出来:

def reverse(data):
    for index in range(len(data)-1, -1, -1):
        yield data[index]

前一节中描述了基于类的迭代器,它能作的每一件事生成器也能作到。因为自动创建了 iter() 和 next() 方法,生成器显得如此简洁。另一个关键的功能在于两次执行之间,局部变量和执行状态都自动的保存下来。这使函数很容易写,而且比使用 self.index 和 self.data 之类的方式更清晰。除了创建和保存程序状态的自动方法,当发生器终结时,还会自动抛出 StopIteration 异常。综上所述,这些功能使得编写一个正规函数成为创建迭代器的最简单方法。

4 Python标准库概览

4.1 操作系统接口

>>> import os
>>> os.getcwd()      # Return the current working directory
'C:\\Python35'
>>> os.chdir('/server/accesslogs')   # Change current working directory
>>> os.system('mkdir today')   # Run the command mkdir in the system shell
0

4.2 文件通配符

glob 模块提供了一个函数用于从目录通配符搜索中生成文件列表:

>>> import glob
>>> glob.glob('*.py')
['primes.py', 'random.py', 'quote.py']

4.3 命令行参数

通用工具脚本经常调用命令行参数。这些命令行参数以链表形式存储于 sys 模块的 argv 变量。例如在命令行中执行 python demo.py one two three 后可以得到以下输出结果:

>>> import sys
>>> print(sys.argv)
['demo.py', 'one', 'two', 'three']

4.4 数学

random提供了生成随机数的工具:

>>> import random
>>> random.choice(['apple', 'pear', 'banana'])
'apple'
>>> random.sample(range(100), 10)   # sampling without replacement
[30, 83, 16, 4, 8, 81, 41, 50, 18, 33]
>>> random.random()    # random float
0.17970987693706186
>>> random.randrange(6)    # random integer chosen from range(6)
4

4.5 互联网访问

有几个模块用于访问互联网以及处理网络通信协议。其中最简单的两个是用于处理从 urls 接收的数据的 urllib.request 以及用于发送电子邮件的 smtplib:

>>> from urllib.request import urlopen
>>> for line in urlopen('http://tycho.usno.navy.mil/cgi-bin/timer.pl'):
...     line = line.decode('utf-8')  # Decoding the binary data to text.
...     if 'EST' in line or 'EDT' in line:  # look for Eastern Time
...         print(line)

<BR>Nov. 25, 09:43:32 PM EST

>>> import smtplib
>>> server = smtplib.SMTP('localhost')
>>> server.sendmail('soothsayer@example.org', 'jcaesar@example.org',
... """To: jcaesar@example.org
... From: soothsayer@example.org
...
... Beware the Ides of March.
... """)
>>> server.quit()

4.6 日期和时间

datetime 模块为日期和时间处理同时提供了简单和复杂的方法。支持日期和时间算法的同时,实现的重点放在更有效的处理和格式化输出。该模块还支持时区处理。

>>> # dates are easily constructed and formatted
>>> from datetime import date
>>> now = date.today()
>>> now
datetime.date(2003, 12, 2)
>>> now.strftime("%m-%d-%y. %d %b %Y is a %A on the %d day of %B.")
'12-02-03. 02 Dec 2003 is a Tuesday on the 02 day of December.'

>>> # dates support calendar arithmetic
>>> birthday = date(1964, 7, 31)
>>> age = now - birthday
>>> age.days
14368

4.7 数据压缩

>>> import zlib
>>> s = b'witch which has which witches wrist watch'
>>> len(s)
41
>>> t = zlib.compress(s)
>>> len(t)
37
>>> zlib.decompress(t)
b'witch which has which witches wrist watch'
>>> zlib.crc32(s)
226805979

4.8 性能度量

>>> from timeit import Timer
>>> Timer('t=a; a=b; b=t', 'a=1; b=2').timeit()
0.57535828626024577
>>> Timer('a,b = b,a', 'a=1; b=2').timeit()
0.54962537085770791

4.9 质量控制

开发高质量软件的方法之一是为每一个函数开发测试代码,并且在开发过程中经常进行测试。doctest 模块提供了一个工具,扫描模块并根据程序中内嵌的文档字符串执行测试。测试构造如同简单的将它的输出结果剪切并粘贴到文档字符串中。通过用户提供的例子,它发展了文档,允许 doctest 模块确认代码的结果是否与文档一致:

def average(values):
    """Computes the arithmetic mean of a list of numbers.

    >>> print(average([20, 30, 70]))
    40.0
    """
    return sum(values) / len(values)

import doctest
doctest.testmod()   # automatically validate the embedded tests

unittest 模块不像 doctest 模块那么容易使用,不过它可以在一个独立的文件里提供一个更全面的测试集:

import unittest

class TestStatisticalFunctions(unittest.TestCase):

    def test_average(self):
        self.assertEqual(average([20, 30, 70]), 40.0)
        self.assertEqual(round(average([1, 5, 7]), 1), 4.3)
        with self.assertRaises(ZeroDivisionError):
            average([])
        with self.assertRaises(TypeError):
            average(20, 30, 70)

unittest.main() # Calling from the command line invokes all tests

4.10 模板

string 提供了一个灵活多变的模版类 Template ,使用它最终用户可以用简单的进行编辑。这使用户可以在不进行改变的情况下定制他们的应用程序。格式使用 $ 为开头的 Python 合法标识(数字、字母和下划线)作为占位符。占位符外面的大括号使它可以和其它的字符不加空格混在一起。 $$ 创建一个单独的 $:

>>> from string import Template
>>> t = Template('${village}folk send $$10 to $cause.')
>>> t.substitute(village='Nottingham', cause='the ditch fund')
'Nottinghamfolk send $10 to the ditch fund.'

当一个占位符在字典或关键字参数中没有被提供时,substitute() 方法就会抛出一个 KeyError 异常。 对于邮件合并风格的应用程序,用户提供的数据可能并不完整,这时使用 safe_substitute() 方法可能更适合 — 如果数据不完整,它就不会改变占位符:

>>> t = Template('Return the $item to $owner.')
>>> d = dict(item='unladen swallow')
>>> t.substitute(d)
Traceback (most recent call last):
  ...
KeyError: 'owner'
>>> t.safe_substitute(d)
'Return the unladen swallow to $owner.'

4.11 十进制浮点数算法

>>> from decimal import *
>>> round(Decimal('0.70') * Decimal('1.05'), 2)
Decimal('0.74')
>>> round(.70 * 1.05, 2)
0.73

Decimal 的结果总是保有结尾的 0,自动从两位精度延伸到4位。Decimal 重现了手工的数学运算,这就确保了二进制浮点数无法精确保有的数据精度。高精度使 Decimal 可以执行二进制浮点数无法进行的模运算和等值测试:

>>> Decimal('1.00') % Decimal('.10')
Decimal('0.00')
>>> 1.00 % 0.10
0.09999999999999995

>>> sum([Decimal('0.1')]*10) == Decimal('1.0')
True
>>> sum([0.1]*10) == 1.0
False

decimal 提供了必须的高精度算法:

>>> getcontext().prec = 36
>>> Decimal(1) / Decimal(7)
Decimal('0.142857142857142857142857142857142857')

5 python3菜鸟教程

5.1 简介

#!/usr/bin/python3
 
print("Hello, World!")

关于实例中第一行代码#!/usr/bin/python3 的理解:
分成两种情况:

(1)如果调用python脚本时,使用:

python script.py 

#!/usr/bin/python 被忽略,等同于注释。

(2)如果调用python脚本时,使用:

./script.py 

#!/usr/bin/python 指定解释器的路径。
#!/usr/bin/python3 是告诉操作系统执行这个脚本的时候,调用 /usr/bin 下的 python3 解释器;相当于写死了python3的路径

#!/usr/bin/env python3 这种用法是为了防止操作系统用户没有将 python3 装在默认的 /usr/bin 路径里。当系统看到这一行的时候,首先会到 env 设置里查找 python3 的安装路径,再调用对应路径下的解释器程序完成操作。

Python 是一种解释型语言: 这意味着开发过程中没有了编译这个环节。类似于PHP和Perl语言。

解释性 – 这一点需要一些解释。一个用编译性语言比如 C 或 C++ 写的程序可以从源文件(即 C 或 C++ 语言)转换到一个你的计算机使用的语言(二进制代码,即0和1)。这个过程通过编译器和不同的标记、选项完成。当你运行你的程序的时候,连接/转载器软件把你的程序从硬盘复制到内存中并且运行。而 Python 语言写的程序不需要编译成二进制代码。你可以直接从源代码运行程序。在计算机内部,Python 解释器把源代码转换成称为字节码的中间形式,然后再把它翻译成计算机使用的机器语言并运行。事实上,由于你不再需要担心如何编译程序,如何确保连接转载正确的库等等,所有这一切使得使用 Python 更加简单。由于你只需要把你的 Python 程序拷贝到另外一台计算机上,它就可以工作了,这也使得你的 Python 程序更加易于移植。

Python 是交互式语言: 这意味着,您可以在一个 Python 提示符 >>> 后直接执行代码。

面向对象 – Python 既支持面向过程的编程也支持面向对象的编程。在“面向过程”的语言中,程序是由过程或仅仅是可重用代码的函数构建起来的。在“面向对象”的语言中,程序是由数据和功能组合而成的对象构建起来的。与其他主要的语言如 C++ 和 Java 相比,Python 以一种非常强大又简单的方式实现面向对象编程。

5.2 Python基础语法

5.2.1 多行语句

Python 通常是一行写完一条语句,但如果语句很长,我们可以使用反斜杠 \ 来实现多行语句,例如:

total = item_one + \
        item_two + \
        item_three

在 [], {}, 或 () 中的多行语句,不需要使用反斜杠 \,例如:

total = ['item_one', 'item_two', 'item_three',
        'item_four', 'item_five']

5.3 数字类型

python中数字有四种类型:整数、布尔型、浮点数和复数。

int (整数), 如 1, 只有一种整数类型 int,表示为长整型,没有 python2 中的 Long。
bool (布尔), 如 True。
float (浮点数), 如 1.23、3E-2
complex (复数), 如 1 + 2j、 1.1 + 2.2j

可以用isinstance来判断

>>> a = 111
>>> isinstance(a, int)
True

isinstance 和 type 的区别在于:

type()不会认为子类是一种父类类型。
isinstance()会认为子类是一种父类类型。

>>> class A:
...     pass
... 
>>> class B(A):
...     pass
... 
>>> isinstance(A(), A)
True
>>> type(A()) == A 
True
>>> isinstance(B(), A)
True
>>> type(B()) == A
False

5.4 字符串(String)

Python 中单引号 ’ 和双引号 " 使用完全相同。

使用三引号(‘’’ 或 “”")可以指定一个多行字符串。

转义符 \。

反斜杠可以用来转义,使用 r 可以让反斜杠不发生转义。 如 r"this is a line with \n" 则 \n 会显示,并不是换行。

按字面意义级联字符串,如 "this " "is " “string” 会被自动转换为 this is string。

字符串可以用 + 运算符连接在一起,用 * 运算符重复。

Python 中的字符串有两种索引方式,从左往右以 0 开始,从右往左以 -1 开始。

Python 中的字符串不能改变。

Python 没有单独的字符类型,一个字符就是长度为 1 的字符串。

字符串的截取的语法格式如下:变量[头下标:尾下标:步长]

print(str[0:-1])         # 输出第一个到倒数第二个的所有字符

5.4 同一行显示多条语句

Python 可以在同一行中使用多条语句,语句之间使用分号 ; 分割,以下是一个简单的实例:

#!/usr/bin/python3
 
import sys; x = 'runoob'; sys.stdout.write(x + '\n')

使用脚本执行以上代码,输出结果为:

runoob

使用交互式命令行执行,输出结果为:

>>> import sys; x = 'runoob'; sys.stdout.write(x + '\n')
runoob
7

此处的 7 表示字符数,runoob 有 6 个字符,\n 表示一个字符,加起来 7 个字符。

5.5 Print输出

print 默认输出是换行的,如果要实现不换行需要在变量末尾加上 end=“”:

#!/usr/bin/python3
 
x="a"
y="b"
# 换行输出
print( x )
print( y )
 
print('---------')
# 不换行输出
print( x, end=" " )
print( y, end=" " )
print()

以上实例执行结果为:

a
b
---------
a b

5.6 import与from…import

在 python 用 import 或者 from…import 来导入相应的模块。

将整个模块(somemodule)导入,格式为: import somemodule

从某个模块中导入某个函数,格式为: from somemodule import somefunction

从某个模块中导入多个函数,格式为: from somemodule import firstfunc, secondfunc, thirdfunc

将某个模块中的全部函数导入,格式为: from somemodule import *

1、将整个模块导入,例如:import time,在引用时格式为:time.sleep(1)。

2、将整个模块中全部函数导入,例如:from time import *,在引用时格式为:sleep(1)。

3、将模块中特定函数导入,例如:from time import sleep,在引用时格式为:sleep(1)。

4、将模块换个别名,例如:import time as abc,在引用时格式为:abc.sleep(1)。

5.7 字符串转为int需先转为float

a='2.1'  # 这是一个字符串
print(int(a))

ValueError: invalid literal for int() with base 10: ‘2.1’,需先把字符串转化为float再转换成int型

a='2.1'
print(int(float(a)))

5.8 python基本数据类型

Python 中的变量不需要声明。每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建。
在 Python 中,变量就是变量,它没有类型,我们所说的"类型"是变量所指的内存中对象的类型。
等号(=)用来给变量赋值。
等号(=)运算符左边是一个变量名,等号(=)运算符右边是存储在变量中的值。例如:

Python允许同时为多个变量赋值

a = b = c = 1 #

以上实例,创建一个整型对象,值为 1,从后向前赋值,三个变量被赋予相同的数值。您也可以为多个对象指定多个变量。例如:

a, b, c = 1, 2, "runoob"

5.9 标准数据类型

Python3有六个标准的数据类型,六个标准数据类型中:

不可变数据(3 个):Number(数字)、String(字符串)、Tuple(元组);

可变数据(3 个):List(列表)、Dictionary(字典)、Set(集合)。

5.10 列表

2/4  #除法,得到一个浮点数0.5
2//4 #除法,得到一个整数0

在混合计算时,Python会把整型转换成为浮点数

翻转字符串

def reverseWords(input):
     
    # 通过空格将字符串分隔符,把各个单词分隔为列表
    inputWords = input.split(" ")
 
    # 翻转字符串
    # 假设列表 list = [1,2,3,4],  
    # list[0]=1, list[1]=2 ,而 -1 表示最后一个元素 list[-1]=4 ( 与 list[3]=4 一样)
    # inputWords[-1::-1] 有三个参数
    # 第一个参数 -1 表示最后一个元素
    # 第二个参数为空,表示移动到列表末尾
    # 第三个参数为步长,-1 表示逆向
    inputWords=inputWords[-1::-1]
 
    # 重新组合字符串
    output = ' '.join(inputWords)
     
    return output
 
if __name__ == "__main__":
    input = 'I like runoob'
    rw = reverseWords(input)
    print(rw)
list = [ 'abcd', 786 , 2.23, 'runoob', 70.2 ]
a = list[2]     #输出2.23
b = list[2:3]   #输出[2.23]
type(a) -> <class 'float'>
type(b) -> <class 'list'>
# 输出结果为

runoob like I

5.11 元组

字符串可以看成是一种特殊的元组,元组虽然不可变,但他可以包含可变的对象,比如list列表;构造包含0个和1个元素的元组比较特殊,所以有一些额外的语法规则:

tup1 = ()      #空元组
tup2 = (20,)   #一个元素,需要在元素后添加逗号

python中的函数可接受可变长参数,比如以“*”开头的参数名,会将所有的参数收集到一个元组上

def test(*args):
    print(args)
    return args

print(type(test(1,2,3,4)))    #可以看见其函数的返回值是一个元组

5.12 集合

可以使用大括号{}或者set()函数创建集合,注意空集合必须用set()而不是{},因为{}是用来创建一个空字典;集合的基本功能是进行成员关系测试和删除重复元素,创建格式:

parame = {value01,value02,...}
或者
set(value)
#!/usr/bin/python3

sites = {'Google', 'Taobao', 'Runoob', 'Facebook', 'Zhihu', 'Baidu'}

print(sites)   # 输出集合,重复的元素被自动去掉

# 成员测试
if 'Runoob' in sites :
    print('Runoob 在集合中')
else :
    print('Runoob 不在集合中')


# set可以进行集合运算
a = set('abracadabra')
b = set('alacazam')

print(a)

print(a - b)     # a 和 b 的差集

print(a | b)     # a 和 b 的并集

print(a & b)     # a 和 b 的交集

print(a ^ b)     # a 和 b 中不同时存在的元素

输出结果

{'Zhihu', 'Baidu', 'Taobao', 'Runoob', 'Google', 'Facebook'}
Runoob 在集合中
{'b', 'c', 'a', 'r', 'd'}
{'r', 'b', 'd'}
{'b', 'c', 'a', 'z', 'm', 'r', 'l', 'd'}
{'c', 'a'}
{'z', 'b', 'm', 'r', 'l', 'd'}

5.13 Dictionary(字典)

列表是有序的对象集合,字典是无序的对象集合。两者的区别在于:字典中的元素是通过键来存取的,而不是通过偏移存取。字典的key必须使用不可变类型。在同一个字典中,key必须是唯一的。

如果把一个字典对象作为for的迭代对象,那么这个操作将会遍历字典的键:

def example(d):
    # d 是一个字典对象
    for c in d:
        print(c)
        #如果调用函数试试的话,会发现函数会将d的所有键打印出来;
        #也就是遍历的是d的键,而不是值.
dict1 = {'abc':1,"cde":2,"d":4,"c":567,"d":"key1"}
for k,v in dict1.items():
    print(k,":",v)

5.14 Python数据类型转换

str(x)   #将对象x转换为字符串
repr(x)  #将对象x转换为表达式字符串
chr(x)   #将一个整数转换为一个字符
ord(x)   #将一个字符转换为它的整数值
hex(x)   #将一个整数转换为一个十六进制字符串
oct(x)   #将一个整数转换为一个八进制字符串

python 与 C 语言和 Java 语言的一点不同,表现在它的变量不需要声明变量类型,这是因为像 C 语言和 Java 语言来说,它们是静态的,而 python 是动态的,变量的类型由赋予它的值来决定,例如:

>>> a = 1
>>> a = 1.001
>>> a = "python"
>>> print(a)
python

Python 中布尔值使用常量 True 和 False 来表示。在数值上下文环境中,True 被当作 1,False 被当作 0,例如:,其他类型值转换 bool 值时除了 ‘’、“”、‘’‘’‘’、“”“”“”、0、()、[]、{}、None、0.0、0L、0.0+0.0j、False 为 False 外,其他都为 True 例如:

>>> bool(-2)
True
>>> bool('')
False

Python 会将较小的数据类型转换为较大的数据类型,以避免数据丢失。
比如整型和浮点型相加会将整型隐式转换为浮点型,而整型和字符串类型的数据相加会报错,无法进行隐式转换,只能显示转换

5.15 变量和对象

对象内存空间,一般最起码有类型和当前被引用次数这两个信息,类型记录了该对象的数据类型,被引用次数记录了该对象内存空间被变量引用的次数;当某对象的被引用次数为0时,Python便会自动回收该对象内存空间

a=10
a='122'
a=[1,2,3]
del a

此时,a在不同的赋值代码行中,引用的对象类型不同,相当于在不断改变a引用的对象,最后当把a变量删除时,其实本质只是删除了a变量名,但由于a引用的[1,2,3]对象,因为a被删除,其被引用次数变为0,也就自动被Python回收,最终表现就是del a时,[1,2,3]也被删除了。

另外一个小知识是,Python为提升代码执行和内存分配效率,会对一些常用的对象提前创建好,并常驻内存,比如下面:

id(4) #不管运行多少次该代码,其返回的值均不变,因为python会保持一些常用的数字常驻内存,不会每次都重新分配内存空间
id('hello world') #每次运行,返回的值均会发生变化,因为每次运行,相当于都在重新分配内存空间

type的意思可以理解为:检查这个变量本身的类型,而不用管他的父类的类型;isinstance的意思可以理解为:判断这个变量的类型是不是属于某一个大类,就好像找家谱一样,判断你是不是这个家族的人;is用来不仅会对比数值是否一样,还会对比类型是否相同,并且是不是对比父类类型

5.16 列表推导式

names = ['Bob','Tom','alice','Jerry','Wendy','Smith']
new_names = [names.upper() for name in names if len(name)>3]
print(new_names)

...
['ALICE','JERRY','WENDY','SMITH']

元组推导式和列表推导式的用法也完全相同,只是元组推导式是用 () 圆括号将各部分括起来,而列表推导式用的是中括号[],另外元组推导式返回的结果是一个生成器对象。

>>> a = (x for x in range(1,10))
>>> a
<generator object <genexpr> at 0x7faf6ee20a50>  # 返回的是生成器对象

>>> tuple(a)       # 使用 tuple() 函数,可以直接将生成器对象转换成元组
(1, 2, 3, 4, 5, 6, 7, 8, 9)

5.17 脚本式编程

在Linux/Unix系统中,你可以在脚本顶部添加以下命令让Python脚本可以像SHELL脚本一样可直接执行:

#! /usr/bin/env python3

然后修改脚本权限,使其有执行权限,命令如下:

$ chmod +x hello.py

执行以下命令:

./hello.py

5.18 算术运算符

// 取整数,向下取接近商的整数

9//2   #4
-9//2  #-5

:=海象运算符,可在表达式内部为变量赋值,Python3.8版本新增运算符

if (n:=len(a)) > 10:
    print(f"List is too long ({n} elements,expected<=10)")

5.19 Python位运算符

按位运算符是把数字看作二进制来进行计算的,Python中的按位运算法则如下:
下表中变量a为60,b为13二进制格式如下

a = 0011 1100

b = 0000 1101

-----------------

a&b = 0000 1100

a|b = 0011 1101

a^b = 0011 0001

~a  = 1100 0011

^ 按位异或运算符:当两对应的二进位相异时,结果为1

5.20 逻辑运算符

and:or:not:

1.and:前面为假(0 或者 False)则表达式为假,否则表达式为后面的值;在 Python 中,逻辑运算符 and,x and y,如果 x 为 True 则返回 y 值。如果 x 为 False 则返回 x 值。如果 x 的值为 True,and 的运算不会结束,会继续看 y 的值,所以此时真与假取决于 y 的值,所以 x 如果为真,则返回 y 的值。如果 x 为假,那么 and 运算就会结束运算过程了,因为有一个为假则 and 为假,所以返回 x 的值。

print(1 and 2)  # 2
print(3 and 0)  # 0
print(0 and 2)  # 0
print(3 and 2)  # 2
print(0 and 0)  # 0

2.or:前面为真(非 0 或者非 False)则表达式为前面的值,否则表达式为后面的值;在 Python 中,逻辑运算符 or,x or y, 如果 x 为 True 则返回 x,如果 x 为 False 返回 y 值。因为如果 x 为 True 那么 or 运算就不需要在运算了,因为一个为真则为真,所以返回 x 的值。如果 x 的值为假,那么 or 运算的结果取决于 y,所以返回 y 的值。

print(1 or 2)   # 1
print(3 or 2)   # 3
print(0 or 2)   # 2
print(0 or 100) # 100
print(0 or 0)

3.混合例子与解析,按照从左向右,优先级高的先执行优先级高的规则,首先因为比较运算符优先级高于逻辑运算符,很简单,如果运算符低于了逻辑运算符优先级那还如何运算呢。and 优先级大于 or,not 优先级大于 and 和 or。

print(0 and 1)      # =>0,0等同于False
print(False and 1)  # =>False
print(-1 and 1)     # =>1
print(1 or False)   # =>1,非零等同于True
print(True or False)# =>True
print(-1 or 0)      # =>-1
not 4 > 5  #True,比较运算符优先级高于逻辑运算符,先算4>5为False,然后not False输出结果为True
Flase and 3False #因为False为假所以and不在运算直接返回False
4 and FalseFalse #因为 4 为真所以 and 运算符会继续运算后面的,以 False 为主,所以返回 False。

优先级:not>and>or

print(1 and 0 or  not False) #=>True
print( not False or 1 and 0) #=>True
print( 1 or not True and 0)  #=>1

5.21 成员运算符

in:如果在指定的序列中找到值返回 True,否则返回 False。
not in:如果在指定的序列中没有找到值返回 True,否则返回 False。

5.21 身份运算符

身份运算符用于比较两个对象的存储单元

is	is 是判断两个标识符是不是引用自一个对象	x is y, 类似 id(x) == id(y) , 如果引用的是同一个对象则返回 True,否则返回 False

is not	is not 是判断两个标识符是不是引用自不同对象	x is not y , 类似 id(x) != id(y)。如果引用的不是同一个对象则返回结果 True,否则返回 False

is 与 == 区别:

is 用于判断两个变量引用对象是否为同一个,是通过id来判断的,当两个基本类型数据(或元组)内容相同时, id 会相同, 但并不代表 a 会随 b 的改变而改变。 == 用于判断引用变量的值是否相等,是通过调用__eq__()来判断的。

>>>a = [1, 2, 3]
>>> b = a
>>> b is a 
True
>>> b == a
True
>>> b = a[:]
>>> b is a
False
>>> b == a
True

1、当列表,元组,字典中的值都引用 a,b 时,总是返回 True,不受 a,b 值大小的影响。

a=1000
b=1000
list1=[a,3,5]
list2=[b,4,5]
print(list1[0] is list2[0])
tuple1=(a,3,5)
tuple2=(b,4,5)
print(tuple1[0] is tuple2[0])
dict1={6:a,2:3,3:5}
dict2={1:b,2:4,3:7}
print(dict1[6] is dict2[1])

输出结果为True,True,True

2、当不引用a,b,直接用具体值来测试时,列表,字典,不受值大小影响,返回True,元组则受 256 值范围的影响,超出范围则地址改变,返回 False。//但在python3.9环境下测试依然是相等

list1=[1000,3,5]
list2=[1000,4,5]
print(list1[0] is list2[0])
tuple1=(1000,3,5)
tuple2=(1000,4,5)
print(tuple1[0] is tuple2[0])
dict1={6:1000,2:3,3:5}
dict2={1:1000,2:4,3:7}
print(dict1[6] is dict2[1])

输出结果为:True,True,True //交互式命令行下则为False

解释:这是因为 Python 程序都是由代码块构成,代码块作为程序的一个最小基本单位来执行。一个模块文件/一个函数体/一个类/交互式命令中的单行代码都叫做一个代码块。

下面的程序中有两部分代码块,一个是名称 a 所在的代码块,一个是名称 b/c 所在的代码块。Python 的另一个优化的地方就是,如果在同一个代码块中创建的两个整数对象中,它们的值相等的话,那么这两个对象引用同一个整数对象。所以Python出于对性能的考虑,但凡是不可变的对象,在同一代码块中,只有值相等的对象就不会重复创建,而是直接引用已经存在的对象。不仅整数对象,字符串对象也遵循同样的原则。

>>> a = 259
>>> def foo () :
...     b = 259
...     c = 259
...     print(a is b)
...     print(b is c)
...
>>> foo()
False
True

3.当直接用列表、元组、字典本身来测试时,刚好相反,元组返回 True,列表,字典返回 False。

list1=[1000,3,5]
list2=[1000,3,5]
print(list1 is list2)
tuple1=(1000,3,5)
tuple2=(1000,3,5)
print(tuple1 is tuple2)
dict1={1:1000,2:3,3:5}
dict2={1:1000,2:3,3:5}
print(dict1 is dict2)

输出结果为:False,True,False

5.22 关于is和==的标识问题

(1)只要是变量的值相同,标识都相同,没有-5~256的限制,看下面的例子:

a = 100000
b = 100000
if a is b:
    print('a 和 b 标识相同,标识为:',id(a))
else:
    print('a 和 b 标识不相同,a 标识为:',id(a),'b 标识为:',id(b))

输出结果为:

a 和 b 标识相同,标识为: 2269983529456

(2)同样的如果是负数,仍然没有上述限制:

a = -100000
b = -100000
if a is b:
    print('a 和 b 标识相同,标识为:',id(a))
else:
    print('a 和 b 标识不相同,a 标识为:',id(a),'b 标识为:',id(b))

输出结果为:

a 和 b 标识相同,标识为: 2269983530992

(3)列表也是一样的,只要是列表项数值一样,那么标识也是一样的。例子如下:

list1 = [10000,20000,30000]
list2 = [10000,12000,15000]
if list1[0] is list2[0]:
    print('list1[0] 和 list2[0] 标识相同,标识为:',id(list1[0]))
else:
    print('list1[0] 和 list2[0] 标识不相同,list1[0]标识为:',id(list1[0]),'list2[0]标识为:',id(list2[0]))

输出结果为:

list1[0] 和 list2[0] 标识相同,标识为: 2269983529712

(4)元组也一样

tuple1 = (10000,20000,30000)
tuple2 = (10000,12000,15000)
if tuple1[0] is tuple2[0]:
    print('tuple1[0] 和 tuple2[0] 标识相同,标识为:',id(tuple1[0]))
else:
    print('tuple1[0] 和 tuple2[0] 标识不相同,tuple1[0] 标识为:',id(tuple1[0]),'tuple2[0]标识为:',id(tuple2[0]))

输出结果为:

tuple1[0] 和 tuple2[0] 标识相同,标识为: 2269983529712

(5)字典和列表是一样的,只要是列表项数值一样,那么标识也是一样的。例子如下:

dict1 = {1:10000,2:20000,3:30000}
dict2 = {1:10000,2:12000,3:15000}
if dict1[1] is dict2[1]:
    print('dict1[1] 和 dict2[1] 标识相同,标识为:',id(dict1[1]))
else:
    print('dict1[1] 和 dict2[1] 标识不相同,dict1[1] 标识为:',id(dict1[1]),'dict2[1] 标识为:',id(dict2[1]))

输出结果为:

dict1[1] 和 dict2[1] 标识相同,标识为: 2804059028656

5.23 运算符优先级

(expressions...),[expressions...], {key: value...}, {expressions...} #圆括号的表达式
x[index], x[index:index], x(arguments...), x.attribute               #读取,切片,调用,属性引用
await x                                                              #await 表达式
**                                                                   #乘方(指数)
+x, -x, ~x                                                           #正,负,按位非 NOT
*, @, /, //, %                                                       #乘,矩阵乘,除,整除,取余
+, -                                                                 #加和减
<<, >>                                                               #移位
&                                                                    #按位与 AND
^                                                                    #按位异或 XOR
|                                                                    #按位或 OR
in,not in, is,is not, <, <=, >, >=, !=, ==                           #比较运算,包括成员检测和标识号检测
not x                                                                #逻辑非 NOT
and                                                                  #逻辑与 AND
or                                                                   #逻辑或 OR
if -- else                                                           #条件表达式
lambda                                                               #lambda 表达式
:=                                                                   #赋值表达式

&、&& 的区别

1、最终结果一样。

2、& 无论左边是 false 还是 true,右边都执行。

3、&& 具有短路效果,左边是 false, 右边不执行。

4、&& 效率更高,项目中推荐使用。

|、||的区别

1、最总的结果一样。

2、|无论左边是false还是true,右边都会执行。

3、||具有短路效果,左边是true,右边不执行。

4、||效果效率更高,项目中推荐使用。

2 进制是以 0b 开头的: 例如: 0b11 则表示十进制的 3
8 进制是以 0o 开头的: 例如: 0o11 则表示十进制的 9
16 进制是以 0x 开头的: 例如: 0x11 则表示十进制的 17
出来的被自动转化成了十进制,分别使用 bin,oct,hex 可输出数字的二进制,八进制,十六进制形式,例如:

>>> a=0b111100
>>> a=60
>>> bin(a)
'0b111100'
>>> oct(a)
'0o74'
>>> hex(a)
'0x3c'

5.24 Python数字类型转换

complex(x) 将x转换到一个复数,实数部分为 x,虚数部分为 0。

complex(x, y) 将 x 和 y 转换到一个复数,实数部分为 x,虚数部分为 y。x 和 y 是数字表达式。

//得到的并不一定是整数类型的数,它与分母分子的数据类型有关系

7//2    #3
7.0//2  #3.0

在交互模式中,最后被输出的表达式结果被赋值给变量_*

>>> tax = 12.5 / 100
>>> price = 100.50
>>> price * tax
12.5625
>>> price + _
113.0625
>>> round(_, 2)
113.06

5.25 数学函数

abs(x)      #返回数字的绝对值,如abs(-10)返回10
fabs(x)     #返回数字的绝对值,如abs(-10)返回10.0 

>>> round(10.5)
10
>>> round(11.5)
12
#Python 所谓的奇进偶弃,因为浮点数的表示在计算机中并不准确,用的时候可能要注意一下。
>>> round(2.675, 2) 
2.67
#按我们的想法返回结果应该是 2.68,可结果却是 2.67,为什么?这跟浮点数的精度有关。我们知道在机器中浮点数不一定能精确表达,因为换算成一串 1 和 0 后可能是无限位数的,机器已经做出了截断处理。那么在机器中保存的2.675这个数字就比实际数字要小那么一点点。这一点点就导致了它离 2.67 要更近一点点,所以保留两位小数时就近似到了 2.67。

5.26 字符串

在 Python 3.8 的版本中可以使用 = 符号来拼接运算表达式与结果:

>>> x = 1
>>> print(f'{x+1}')   # Python 3.6
2

>>> x = 1
>>> print(f'{x+1=}')   # Python 3.8
x+1=2

避免了以往的%s %d等

>>> name = 'Runoob'
>>> 'Hello %s' % name
'Hello Runoob'

使用格式化符号进行进制转换

>>> num=10
>>> print('十六进制:%#x' % num)    #使用%x将十进制num格式化为十六进制
十六进制:0xa
>>> print('二进制:', bin(num))      #使用bin将十进制num格式化为二进制
二进制: 0b1010
>>> print('八进制:%#o' % num)      #使用%o将十进制num格式化为八进制
八进制:0o12

上面使用格式化符号进行进制转换中,多加入了一个#号,目的是在转换结果头部显示当前进制类型,如不需要,可将#号去除,如下

>>> print('八进制:%o' % num)
八进制:12
>>> print('十六进制:%x' % num)
十六进制:a

从左向右遇到分隔符把字符串分割成两部分,返回头、分割符、尾三部分的三元组。如果没有找到分割符,就返回头、尾两个空元素的三元组。

s1 = "I'm a good sutdent."
#以'good'为分割符,返回头、分割符、尾三部分。
s2 = s1.partition('good')
#没有找到分割符'abc',返回头、尾两个空元素的元组。
s3 = s1.partition('abc')

print(s1)
print(s2)
print(s3)

结果如下:

I'm a good sutdent.
("I'm a ", 'good', ' sutdent.')
("I'm a good sutdent.", '', '')

针对 Counter 的升级使用,示例如下:

#必须引用如下库
from collections import Counter

#定义两个字符串变量
Var1 = "1116122137143151617181920849510"
Var2 = "1987262819009787718192084951"

#以字典的形式,输出每个字符串中出现的字符及其数量
print (Counter(Var1))
print (Counter(Var2))

输出如下:

Counter({'1': 12, '2': 3, '6': 2, '3': 2, '7': 2, '4': 2, '5': 2, '8': 2, '9': 2, '0': 2})
Counter({'1': 5, '9': 5, '8': 5, '7': 4, '2': 3, '0': 3, '6': 1, '4': 1, '5': 1})

关于 str() 和 repr() 的区别,str()和repr()输出的都是 str 类型,但是 str() 更注重可读性,repr() 更注重数据本身的信息:

str() 的输出追求可读性,输出格式要便于理解,适合用于输出内容到用户终端。

repr() 的输出追求明确性,除了对象内容,还需要展示出对象的数据类型信息,适合开发和调试阶段使用。

>>> from datetime import datetime
>>> now = datetime.now()
>>> print(str(now))
2017-04-22 15:41:33.012917
>>> print(repr(now))
datetime.datetime(2017, 4, 22, 15, 41, 33, 12917)

5.27 列表

列表元素为字符串时,每个字符串元素的第一个字符的 ASCII 的大小

list1 = ['我最','爱学习','python']
print( 'list1 的最大值:', max(list1)) # list1 的最大值: 爱学习

print(ord(list1[0][0])) # ord('我') >>> 25105
print(ord(list1[1][0])) # ord('爱') >>> 29233
print(ord(list1[2][0])) # ord('p') >>> 112

以下实例演示了通过指定列表中的元素排序来输出列表:
list.sort( key=None, reverse=False)

#!/usr/bin/python
 
# 获取列表的第二个元素
def takeSecond(elem):
    return elem[1]
 
# 列表
random = [(2, 2), (3, 4), (4, 1), (1, 3)]
 
# 指定第二个元素排序
random.sort(key=takeSecond)
 
# 输出类别
print ('排序列表:', random)

以上实例输出结果如下:

排序列表:[(4, 1), (2, 2), (1, 3), (3, 4)]

sort 方法不适合 int 和 str 类型的比较。会报错

(1)根据列表中类对象的属性排序:

class element(object):  
    def __init__(self,id="",name=""):  
        self.id=id  
        self.name=name  
    def __lt__(self, other): # override <操作符  
        if self.id<other.id:  
            return True  
        return False  
  
    def __str__(self): # override __str__  
        return "id={0},name={1}".format(self.id,self.name)  
  
def sort_by_attribute():  
    list=[element(id="130",name="json"),  
          element(id="01",name="jack"),element(id="120",name="tom")]  
    list.sort()  
    for item in list:  
        print(item)

由于 list.sort() 函数在排序时,使用的是小于号对比,所以自定义的数据类型需要 override lt(小于) 函数才能实现排序。根据 element 的 id 属性排序;排序列的结果:

id=01,name=jack  
id=120,name=tom  
id=130,name=json

(2)根据列表中元素的长度排序

def list_sort_by_length():  
    list=["delphi","Delphi","python","Python","c++","C++","c","C","golang","Golang"]  
    list.sort(key=lambda ele:len(ele)) #按元素长度顺序升序排列  
    print("升序:",list)  
  
    list.sort(key=lambda ele:len(ele),reverse=True) #按降序排列  
    print("降序:",list)

借助于 lambda 表达式,计算 list 列表中的元素的长度,根据元素的长度进行排序。排序的结果如下:

升序: ['c', 'C', 'c++', 'C++', 'delphi', 'Delphi', 'python', 'Python', 'golang', 'Golang']  
降序: ['delphi', 'Delphi', 'python', 'Python', 'golang', 'Golang', 'c++', 'C++', 'c', 'C']

(3)根据列表中元素的多个属性进行排序:

def two_d_list_sort():  
    list=[ ["1","c++","demo"],  
           ["1","c","test"],  
           ["2","java",""],  
           ["8","golang","google"],  
           ["4","python","gil"],  
           ["5","swift","apple"]  
    ]  
    list.sort(key=lambda ele:ele[0])# 根据第1个元素排序  
    print(list)  
    list.sort(key=lambda ele:ele[1]) #先根据第2个元素排序  
    print(list)  
    list.sort(key=lambda ele:ele[1]+ele[0]) #先根据第2个元素排序,再根据第1个元素排序  
    print(list)

同样借助于 lambda 表达式完成,当然也可以定义一个与 labmda 具有相同意义的函数实现排序。排序结果:

[['1', 'c++', 'demo'], ['1', 'c', 'test'], ['2', 'java', ''], ['4', 'python', 'gil'], ['5', 'swift', 'apple'], ['8', 'golang', 'google']]  
[['1', 'c', 'test'], ['1', 'c++', 'demo'], ['8', 'golang', 'google'], ['2', 'java', ''], ['4', 'python', 'gil'], ['5', 'swift', 'apple']]  
[['1', 'c++', 'demo'], ['1', 'c', 'test'], ['8', 'golang', 'google'], ['2', 'java', ''], ['4', 'python', 'gil'], ['5', 'swift', 'apple']]

5.28 元组

元组与列表类似,不同之处在于元组的元素不能修改

#创建空元组
tup1 = ()

元组中只包含一个元素时,需要在元素后面加逗号,否则括号会被当作运算符使用

>>> tup1 = (50)
>>> type(tup1)     # 不加逗号,类型为整型
<class 'int'>

>>> tup1 = (50,)
>>> type(tup1)     # 加上逗号,类型为元组
<class 'tuple'>

元组中的元素值是不允许修改的,但我们可以对元组进行连接组合,如下实例:

#!/usr/bin/python3
 
tup1 = (12, 34.56)
tup2 = ('abc', 'xyz')
 
# 以下修改元组元素操作是非法的。
# tup1[0] = 100
 
# 创建一个新的元组
tup3 = tup1 + tup2
print (tup3)

删除元组

#!/usr/bin/python3
 
tup = ('Google', 'Runoob', 1997, 2000)
 
print (tup)
del tup
print ("删除后的元组 tup : ")
print (tup)

以上实例元组被删除后,输出变量会有异常信息,输出如下所示:

删除后的元组 tup : 
Traceback (most recent call last):
  File "test.py", line 8, in <module>
    print (tup)
NameError: name 'tup' is not defined

所谓元组的不可变指的是元组所指向的内存中的内容不可变。

>>> tup = ('r', 'u', 'n', 'o', 'o', 'b')
>>> tup[0] = 'g'     # 不支持修改元素
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> id(tup)     # 查看内存地址
4440687904
>>> tup = (1,2,3)
>>> id(tup)
4441088800    # 内存地址不一样了

从以上实例可以看出,重新赋值的元组 tup,绑定到新的对象了,不是修改了原来的对象。tuple所谓的"不变"是说,tuple的每个元素,指向永远不变。即指向’a’,就不能改成指向’b’,指向一个list,就不能改成指向其他对象,但指向的这个list本身是可变的!

因为元组的局限性:不能为元组内部的数据进行命名,所以往往我们并不知道一个元组所要表达的意义,所以在这里引入了 collections.namedtuple 这个工厂函数,来构造一个带字段名的元组。具名元组的实例和普通元组消耗的内存一样多,因为字段名都被存在对应的类里面。这个类跟普通的对象实例比起来也要小一些,因为 Python 不会用 dict 来存放这些实例的属性。namedtuple 对象的定义如以下格式:

collections.namedtuple(typename, field_names, verbose=False, rename=False) 
from collections import namedtuple
# 定义一个namedtuple类型User,并包含name,sex和age属性。
User = namedtuple('User', ['name', 'sex', 'age'])
# 创建一个User对象
user = User(name='Runoob', sex='male', age=12)
# 获取所有字段名
print( user._fields )
# 也可以通过一个list来创建一个User对象,这里注意需要使用"_make"方法
user = User._make(['Runoob', 'male', 12])
print( user )
# 获取用户的属性
print( user.name )
print( user.sex )
print( user.age )
# 修改对象属性,注意要使用"_replace"方法
user = user._replace(age=22)
print( user )
# 将User对象转换成字典,注意要使用"_asdict"
print( user._asdict() )

以上实例输出结果为:

('name', 'sex', 'age')
User(name='Runoob', sex='male', age=12)
Runoob
male
12
User(name='Runoob', sex='male', age=22)
OrderedDict([('name', 'Runoob'), ('sex', 'male'), ('age', 22)])

Python中,元组装包拆包是自动的,不需要任何函数,导致很多人对于函数返回值一会有括号一会没括号非常迷惑;先看代码:

a=1,2,3
#它其实等价于下面的代码
a=(1,2,3)
#因为等号左边只有1个变量,而等号右边有3个值,因此自动装包成为一个元组

再看下面的代码:

a,b,c=(1,2,3)
#自动拆包,得到a=1,b=2,c=3

当函数return的时候,其实只能return一个值,并不能return多个值;有人会问,我return了多个值也没有报错啊,运行很正常;那正是因为Python将多个返回值自动装包造成的;因此当你返回多个变量,而外面只用一个变量去接收,会接收到一个元组;而当你用多个变量去接,就能对应的接收到每个值,这是因为自动拆包;

a=10
b=20
a,b=b,a,1

执行会报错:too many values to unpack;这里很明显的,告诉你元组里有3个值需要拆包,而你只用2个值去接收,证明了元组确实执行了一个拆包的动作

5.29 字典键的特性

字典值可以是任何的 python 对象,既可以是标准的对象,也可以是用户定义的,但键不行。两个重要的点需要记住:

1)不允许同一个键出现两次。创建时如果同一个键被赋值两次,后一个值会被记住,如下实例:

#!/usr/bin/python3
 
tinydict = {'Name': 'Runoob', 'Age': 7, 'Name': '小菜鸟'}
 
print ("tinydict['Name']: ", tinydict['Name'])

以上实例输出结果:

tinydict['Name']:  小菜鸟

2)键必须不可变,所以可以用数字,字符串或元组充当,而用列表就不行

字典直接复制和copy的区别

#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
dict1 =  {'user':'runoob','num':[1,2,3]}
 
dict2 = dict1          # 浅拷贝: 引用对象
dict3 = dict1.copy()   # 浅拷贝:深拷贝父对象(一级目录),子对象(二级目录)不拷贝,子对象是引用
 
# 修改 data 数据
dict1['user']='root'
dict1['num'].remove(1)
 
# 输出结果
print(dict1)
print(dict2)
print(dict3)

实例中 dict2 其实是 dict1 的引用(别名),所以输出结果都是一致的,dict3 父对象进行了深拷贝,不会随dict1 修改而修改,子对象是浅拷贝所以随 dict1 的修改而修改。

{'user': 'root', 'num': [2, 3]}
{'user': 'root', 'num': [2, 3]}
{'user': 'runoob', 'num': [2, 3]}

字典get方法

#!/usr/bin/python

tinydict = {'Name': 'Runoob', 'Age': 27}

print ("Age : ", tinydict.get('Age'))

# 没有设置 Sex,也没有设置默认的值,输出 None
print ("Sex : ", tinydict.get('Sex'))  

# 没有设置 Salary,输出默认的值  0.0
print ('Salary: ', tinydict.get('Salary', 0.0))

以上实例输出结果为:

Age : 27
Sex : None
Salary: 0.0

get() 方法 Vs dict[key] 访问元素区别:

get(key) 方法在 key(键)不在字典中时,可以返回默认值 None 或者设置的默认值。

dict[key] 在 key(键)不在字典中时,会触发 KeyError 异常。

get() 方法对嵌套字典的使用方法如下:

#!/usr/bin/python

tinydict = {'RUNOOB' : {'url' : 'www.runoob.com'}}

res = tinydict.get('RUNOOB', {}).get('url')
# 输出结果
print("RUNOOB url 为 : ", str(res))

以上实例输出结果为:

RUNOOB url 为 :  www.runoob.com

Python3 字典 update() 方法:

Python 字典 update() 函数把字典参数 dict2 的 key/value(键/值) 对更新到字典 dict 里。

#!/usr/bin/python3
 
tinydict = {'Name': 'Runoob', 'Age': 7}
tinydict2 = {'Sex': 'female' }
 
tinydict.update(tinydict2)
print ("更新字典 tinydict : ", tinydict)

以上实例输出结果为:

更新字典 tinydict :  {'Name': 'Runoob', 'Age': 7, 'Sex': 'female'}

Python3 字典 values() 方法返回一个视图对象。

>>> #相同两个 dict.values() 比较返回都是 False
>>> d = {'a': 1}
>>> d.values() == d.values()
False

字典可以通过以下方法调换 key和 value,当然要注意原始 value 的类型,必须是不可变类型:

dic = {
    'a': 1,
    'b': 2,
    'c': 3,
}

reverse = {v: k for k, v in dic.items()}

print(dic)
print(reverse)

输出如下:

{'a': 1, 'b': 2, 'c': 3}
{1: 'a', 2: 'b', 3: 'c'}

5.30 集合

1.添加元素

s.add(x)

将元素 x 添加到集合 s 中,如果元素已存在,则不进行任何操作。还有一个方法,也可以添加元素,且参数可以是列表,元组,字典等,语法格式如下:

s.update( x )

x 可以有多个,用逗号分开。

>>> thisset = set(("Google", "Runoob", "Taobao"))
>>> thisset.update({1,3})
>>> print(thisset)
{1, 3, 'Google', 'Taobao', 'Runoob'}
>>> thisset.update([1,4],[5,6])  
>>> print(thisset)
{1, 3, 4, 5, 6, 'Google', 'Taobao', 'Runoob'}

2.移除元素

s.remove( x )

将元素 x 从集合 s 中移除,如果元素不存在,则会发生错误。此外还有一个方法也是移除集合中的元素,且如果元素不存在,不会发生错误。格式如下所示:

s.discard( x )

>>> thisset = set(("Google", "Runoob", "Taobao"))
>>> thisset.discard("Facebook")  # 不存在不会发生错误
>>> print(thisset)
{'Taobao', 'Google', 'Runoob'}

我们也可以设置随机删除集合中的一个元素,语法格式如下:

s.pop() # 多次执行测试结果都不一样

3.计算集合元素个数

len(s)

>>> thisset = set(("Google", "Runoob", "Taobao","Taobao"))
>>> len(thisset)
3

difference() 方法用于返回集合的差集,即返回的集合元素包含在第一个集合中,但不包含在第二个集合(方法的参数)中。

x = {"apple", "banana", "cherry"}
y = {"google", "microsoft", "apple"}
 
z = x.difference(y) 
 
print(z)

{'cherry', 'banana'}

intersection_update() 方法用于获取两个或更多集合中都重叠的元素,即计算交集。intersection_update() 方法不同于 intersection() 方法,因为 intersection() 方法是返回一个新的集合,而 intersection_update() 方法是在原始的集合上移除不重叠的元素。

x = {"a", "b", "c"}
y = {"c", "d", "e"}
z = {"f", "g", "c"}
 
x.intersection_update(y, z)
 
print(x)

{'c'}

difference() 方法用于返回集合的差集,即返回的集合元素包含在第一个集合中,但不包含在第二个集合(方法的参数)中。

x = {"apple", "banana", "cherry"}
y = {"google", "microsoft", "apple"}
 
z = x.difference(y) 
 
print(z)

{'cherry', 'banana'}

difference_update() 方法用于移除两个集合中都存在的元素。difference_update() 方法与 difference() 方法的区别在于 difference() 方法返回一个移除相同元素的新集合,而 difference_update() 方法是直接在原来的集合中移除元素,没有返回值。

x = {"apple", "banana", "cherry"}
y = {"google", "microsoft", "apple"}
 
x.difference_update(y) 
 
print(x)

{'cherry', 'banana'}

isdisjoint() 方法用于判断两个集合是否包含相同的元素,如果没有返回 True,否则返回 False。。

x = {"apple", "banana", "cherry"}
y = {"google", "runoob", "facebook"}
 
z = x.isdisjoint(y) 
 
print(z)

True

issuperset() 方法用于判断指定集合的所有元素是否都包含在原始的集合中,如果是则返回 True,否则返回 False。和issubset()方法的参数正好相反

x = {"f", "e", "d", "c", "b", "a"}
y = {"a", "b", "c"}
 
z = x.issuperset(y) 
 
print(z)

True


x = {"f", "e", "d", "c", "b"}
y = {"a", "b", "c"}
 
z = x.issuperset(y) 
 
print(z)

False

symmetric_difference() 方法返回两个集合中不重复的元素集合,即会移除两个集合中都存在的元素。返回两个集合组成的新集合,但会移除两个集合的重复元素:

x = {"apple", "banana", "cherry"}
y = {"google", "runoob", "apple"}
 
z = x.symmetric_difference(y) 
 
print(z)

{'google', 'cherry', 'banana', 'runoob'}

symmetric_difference_update() 方法移除当前集合中在另外一个指定集合相同的元素,并将另外一个指定集合中不同的元素插入到当前集合中。

x = {"apple", "banana", "cherry"}
y = {"google", "runoob", "apple"}
 
x.symmetric_difference_update(y) 
 
print(x)

{'google', 'cherry', 'banana', 'runoob'}

s.update( “字符串” ) 与 s.update( {“字符串”} ) 含义不同:

s.update( {“字符串”} ) 将字符串添加到集合中,有重复的会忽略。
s.update( “字符串” ) 将字符串拆分单个字符后,然后再一个个添加到集合中,有重复的会忽略。

>>> thisset = set(("Google", "Runoob", "Taobao"))
>>> print(thisset)
{'Google', 'Runoob', 'Taobao'}
>>> thisset.update({"Facebook"})
>>> print(thisset) 
{'Google', 'Runoob', 'Taobao', 'Facebook'}
>>> thisset.update("Yahoo")
>>> print(thisset)
{'h', 'o', 'Facebook', 'Google', 'Y', 'Runoob', 'Taobao', 'a'}
#1.创建一个含有一个元素的集合
>>> my_set = set(('apple',))
>>> my_set
{'apple'}

#2.创建一个含有多个元素的集合
>>> my_set = set(('apple','pear','banana'))
>>> my_set
{'apple', 'banana', 'pear'}

#3.如无必要,不要写成如下形式
>>> my_set = set('apple')
>>> my_set
{'l', 'e', 'p', 'a'}
>>> my_set1 = set(('apple'))
>>> my_set1
{'l', 'e', 'p', 'a'}
#1、对于 python 中列表 list、tuple 类型中的元素,转换集合是,会去掉重复的元素如下:(而且集合对list与tuple具有排序(升序)功能)
>>> list = [1,1,2,3,4,5,3,1,4,6,5]
>>> set(list)
{1, 2, 3, 4, 5, 6}
>>> tuple = (2,3,5,6,3,5,2,5)
>>> set(tuple)
{2, 3, 5, 6}

6 Python编程

6.1 赋值

#!/usr/bin/python3
 
# Fibonacci series: 斐波纳契数列
# 两个元素的总和确定了下一个数
a, b = 0, 1
while b < 1000:
    print(b, end=',')
    a, b = b, a+b

执行以上程序,输出结果为:

1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,

其中代码 a, b = b, a+b 的计算方式为先计算右边表达式,然后同时赋值给左边,等价于:

n=b
m=a+b
a=n
b=m

print() sep 参数使用:

>>> a=10;b=388;c=98
>>> print(a,b,c,sep='@')
10@388@98

7 迭代器与生成器

迭代器是一个可以记住遍历的位置的对象。
迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。迭代器有两个基本的方法:iter() 和 next()。字符串,列表或元组对象都可用于创建迭代器:

>>> list=[1,2,3,4]
>>> it = iter(list)    # 创建迭代器对象
>>> print (next(it))   # 输出迭代器的下一个元素
1
>>> print (next(it))
2

迭代器对象可以使用常规for语句进行遍历:

#!/usr/bin/python3
 
list=[1,2,3,4]
it = iter(list)    # 创建迭代器对象
for x in it:
    print (x, end=" ")

也可以使用 next() 函数:

#!/usr/bin/python3
 
import sys         # 引入 sys 模块
 
list=[1,2,3,4]
it = iter(list)    # 创建迭代器对象
 
while True:
    try:
        print (next(it))
    except StopIteration:
        sys.exit()

以上都会输出 1 2 3 4

7.1 创建一个迭代器

把一个类作为一个迭代器使用需要在类中实现两个方法 iter() 与 next() 。

创建一个返回数字的迭代器,初始值为 1,逐步递增 1:

class MyNumbers:
  def __iter__(self):
    self.a = 1
    return self
 
  def __next__(self):
    x = self.a
    self.a += 1
    return x
 
myclass = MyNumbers()
myiter = iter(myclass)
 
print(next(myiter))
print(next(myiter))
print(next(myiter))
print(next(myiter))
print(next(myiter))

执行输出结果为:1 2 3 4 5

7.2 StopIteration

StopIteration 异常用于标识迭代的完成,防止出现无限循环的情况,在 next() 方法中我们可以设置在完成指定循环次数后触发 StopIteration 异常来结束迭代。

在 20 次迭代后停止执行:

class MyNumbers:
  def __iter__(self):
    self.a = 1
    return self
 
  def __next__(self):
    if self.a <= 20:
      x = self.a
      self.a += 1
      return x
    else:
      raise StopIteration
 
myclass = MyNumbers()
myiter = iter(myclass)
 
for x in myiter:
  print(x)

执行输出结果为:1 2 3 … 18 19 20

7.3 生成器

在python中,使用了yield的函数被称为生成器(generator),跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。调用一个生成器函数,返回的是一个迭代器对象。以下实例使用 yield 实现斐波那契数列:

#!/usr/bin/python3
 
import sys
 
def fibonacci(n): # 生成器函数 - 斐波那契
    a, b, counter = 0, 1, 0
    while True:
        if (counter > n): 
            return
        yield a
        a, b = b, a + b
        counter += 1
f = fibonacci(10) # f 是一个迭代器,由生成器返回生成
 
while True:
    try:
        print (next(f), end=" ")
    except StopIteration:
        sys.exit()

8 函数

8.1 参数传递

在 python 中,类型属于对象,对象有不同类型的区分,变量是没有类型的:

a=[1,2,3]

a="Runoob"

以上代码中,[1,2,3] 是 List 类型,“Runoob” 是 String 类型,而变量 a 是没有类型,她仅仅是一个对象的引用(一个指针),可以是指向 List 类型对象,也可以是指向 String 类型对象。

8.2 可更改(mutable)与不可更改(immutable)对象

在 python 中,strings, tuples, 和 numbers 是不可更改的对象,而 list,dict 等则是可以修改的对象。

不可变类型:变量赋值 a=5 后再赋值 a=10,这里实际是新生成一个 int 值对象 10,再让 a 指向它,而 5 被丢弃,不是改变 a 的值,相当于新生成了 a。

可变类型:变量赋值 la=[1,2,3,4] 后再赋值 la[2]=5 则是将 list la 的第三个元素值更改,本身la没有动,只是其内部的一部分值被修改了。

python 函数的参数传递:

不可变类型:类似 C++ 的值传递,如整数、字符串、元组。如 fun(a),传递的只是 a 的值,没有影响 a 对象本身。如果在 fun(a) 内部修改 a 的值,则是新生成一个 a 的对象。

可变类型:类似 C++ 的引用传递,如 列表,字典。如 fun(la),则是将 la 真正的传过去,修改后 fun 外部的 la 也会受影响

python 中一切都是对象,严格意义我们不能说值传递还是引用传递,我们应该说传不可变对象和传可变对象。

(1)传不可变对象实例

def change(a):
    print(id(a))   # 指向的是同一个对象
    a=10
    print(id(a))   # 一个新对象
 
a=1
print(id(a))
change(a)

以上实例输出结果为:

4379369136
4379369136
4379369424

可以看见在调用函数前后,形参和实参指向的是同一个对象(对象 id 相同),在函数内部修改形参后,形参指向的是不同的 id。

(2)传可变对象实例
可变对象在函数里修改了参数,那么在调用这个函数的函数里,原始的参数也被改变了。例如:

#!/usr/bin/python3
 
# 可写函数说明
def changeme( mylist ):
   "修改传入的列表"
   mylist.append([1,2,3,4])
   print ("函数内取值: ", mylist)
   return
 
# 调用changeme函数
mylist = [10,20,30]
changeme( mylist )
print ("函数外取值: ", mylist)

传入函数的和在末尾添加新内容的对象用的是同一个引用。故输出结果如下:

函数内取值:  [10, 20, 30, [1, 2, 3, 4]]
函数外取值:  [10, 20, 30, [1, 2, 3, 4]]

8.3 不定长参数

你可能需要一个函数能处理比当初声明时更多的参数。这些参数叫做不定长参数,和上述 2 种参数不同,声明时不会命名。基本语法如下:加了星号 * 的参数会以元组(tuple)的形式导入,存放所有未命名的变量参数。

#!/usr/bin/python3
  
# 可写函数说明
def printinfo( arg1, *vartuple ):
   "打印任何传入的参数"
   print ("输出: ")
   print (arg1)
   print (vartuple)
 
# 调用printinfo 函数
printinfo( 70, 60, 50 )

以上实例输出结果:

输出: 
70
(60, 50)

如果在函数调用时没有指定参数,它就是一个空元组。我们也可以不向函数传递未命名的变量。如下实例:

printinfo( 10 )  # 输出10

加了两个星号 ** 的参数会以字典的形式导入。

#!/usr/bin/python3
  
# 可写函数说明
def printinfo( arg1, **vardict ):
   "打印任何传入的参数"
   print ("输出: ")
   print (arg1)
   print (vardict)
 
# 调用printinfo 函数
printinfo(1, a=2,b=3)

以上实例输出结果:

输出: 
1
{'a': 2, 'b': 3}

声明函数时,参数中星号 * 可以单独出现,例如:

def f(a,b,*,c):
    return a+b+c

如果单独出现星号 *,则星号 * 后的参数必须用关键字传入:

>>> def f(a,b,*,c):
...     return a+b+c
... 
>>> f(1,2,3)   # 报错
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() takes 2 positional arguments but 3 were given
>>> f(1,2,c=3) # 正常
6

8.4 匿名函数

Python 使用 lambda 来创建匿名函数。所谓匿名,意即不再使用 def 语句这样标准的形式定义一个函数。

lambda 的主体是一个表达式,而不是一个代码块。仅仅能在 lambda 表达式中封装有限的逻辑进去。lambda 函数拥有自己的命名空间,且不能访问自己参数列表之外或全局命名空间里的参数。

虽然 lambda 函数看起来只能写一行,却不等同于 C 或 C++ 的内联函数,后者的目的是调用小函数时不占用栈内存从而增加运行效率。

lambda 函数的语法只包含一个语句,如下:

lambda [arg1 [,arg2,.....argn]]:expression
x = lambda a : a + 10
print(x(5))

以上实例输出结果 15

以下实例匿名函数设置两个参数:

#!/usr/bin/python3
# 可写函数说明
sum = lambda arg1, arg2: arg1 + arg2
# 调用sum函数
print ("相加后的值为 : ", sum( 10, 20 ))
print ("相加后的值为 : ", sum( 20, 20 ))

以上实例输出结果:

相加后的值为 :  30
相加后的值为 :  40

我们可以将匿名函数封装在一个函数内,这样可以使用同样的代码来创建多个匿名函数。以下实例将匿名函数封装在 myfunc 函数中,通过传入不同的参数来创建不同的匿名函数:

def myfunc(n):
  return lambda a : a * n
 
mydoubler = myfunc(2)
mytripler = myfunc(3)
 
print(mydoubler(11))
print(mytripler(11))

以上实例输出结果:

22 33

lambda 匿名函数也是可以使用"关键字参数"进行参数传递

>>> g= lambda x,y : x**2+y**2
>>> g(2,3)
13
>>> g(y=3,x=2)
13

同样地,lambda 匿名函数也可以设定默认值

>>> g= lambda x=0,y=0 : x**2+y**2
>>> g(2,3)
13
>>> g(2)
4
>>> g(y=3)
9

8.5 强制位置参数

Python3.8 新增了一个函数形参语法 / 用来指明函数形参必须使用指定位置参数,不能使用关键字参数的形式。在以下的例子中,形参 a 和 b 必须使用指定位置参数,c 或 d 可以是位置形参或关键字形参,而 e 和 f 要求为关键字形参:

def f(a, b, /, c, d, *, e, f):
    print(a, b, c, d, e, f)

以下使用方法是正确的:

f(10, 20, 30, d=40, e=50, f=60)

以下使用方法会发生错误:

f(10, b=20, c=30, d=40, e=50, f=60)   # b 不能使用关键字参数的形式
f(10, 20, 30, 40, 50, f=60)           # e 必须使用关键字参数的形式

默认参数必须放在最后面,否则会报错

8.6 变量作用域

对于变量作用域,变量的访问以 L(Local) –> E(Enclosing) –> G(Global) –>B(Built-in) 的规则查找,即:在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内建中找。

观察以下几个例子,均从内部函数输出变量 x:

#1. 局部作用域
x = int(3.3)
x = 0
def outer():
    x = 1
    def inner():
        x = 2
        print(x)
    inner()

outer()
#执行结果为 2,因为此时直接在函数 inner 内部找到了变量 x。

#2.闭包函数外的函数中
x = int(3.3)
x = 0
def outer():
    x = 1
    def inner():
        i = 2
        print(x)
    inner()

outer()
#执行结果为 1,因为在内部函数 inner 中找不到变量 x,继续去局部外的局部——函数 outer 中找,这时找到了,输出 1。

#3.全局作用域
x = int(3.3)
x = 0
def outer():
    o = 1
    def inner():
        i = 2
        print(x)
    inner()

outer()
#执行结果为 0,在局部(inner函数)、局部的局部(outer函数)都没找到变量 x,于是访问全局变量,此时找到了并输出。

#4. 内建作用域

x = int(3.3)
g = 0
def outer():
    o = 1
    def inner():
        i = 2
        print(x)
    inner()

outer()
#执行结果为 3,在局部(inner函数)、局部的局部(outer函数)以及全局变量中都没有找到变量x,于是访问内建变量,此时找到了并输出。
#函数内可以访问全局变量,但不能更新(修改)其值!
#例 :

a = 10
def sum ( n ) :
   n += a #仅仅访问全局变量a
   print ('a = ', a, end = ' , ' )
   print ( 'n = ', n )
  
sum(3)
#输出 :

a =  10 , n =  13

#如果引用了还没更新的值则会报错 :

a = 10
def sum ( n ) :
   n += a
   a = 11 #修改同名的全局变量,则python会认为他是一个局部变量
   print ('a = ', a, end = ' , ' )
   print ( 'n = ', n )
  
sum(3)
#输出 :

...
UnboundLocalError: local variable 'a' referenced before assignment
#可以加上 global 引用以更新变量值 :

a = 10
def sum ( n ) :
   global a
   n += a
   a = 11
   print ('a = ', a, end = ' , ' )`
   print ( 'n = ', n )

sum ( 3 )
print ( '外 a = ', a )
输出:

a = 11 , n = 13 外 a = 11

在这里补充一点关于 global 和 nonlocal 的知识:nonlocal 只能修改外层函数的变量而不能修改外层函数所引用的全局变量,给一个例子如下:

x = 0
def outer():
    global x
    x = 1    
    def inner():
        nonlocal x
        x = 2        
        print(x)
    inner()

outer()
print(x)
line 6
    nonlocal x
    ^
SyntaxError: no binding for nonlocal 'x' found

修改成下面则可以

x = 0
def outer():

    x = 1    
    def inner():
        nonlocal x
        x = 2        
        print(x)
    inner()

outer()
print(x)

结果为 2 0

global 关键字会跳过中间层直接将嵌套作用域内的局部变量变为全局变量:

num = 20
def outer():
    num = 10
    def inner():
        global num
        print (num)
        num = 100
        print (num)
    inner()
    print(num)
outer()
print (num)

结果如下:

20
100
10
100

在编写函数的过程中,可以显式指定函数的参数类型及返回值类型

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

def function_demo(param_A: int, param_B: float, param_C: list, param_D: tuple) -> dict:
    pass

不按上面的类型传参也可以,比如传两个字符串会拼接在一起不报错,但传一个字符串一个数字会报错

8.7 列表方法

list.insert(i, x) 在指定位置插入一个元素。第一个参数是准备插入到其前面的那个元素的索引,例如 a.insert(0, x) 会插入到整个列表之前,而 a.insert(len(a), x) 相当于 a.append(x)

list.pop([i]) 从列表的指定位置移除元素,并将其返回。如果没有指定索引,a.pop()返回最后一个元素。元素随即从列表中被移除。(方法中 i 两边的方括号表示这个参数是可选的,而不是要求你输入一对方括号,你会经常在 Python 库参考手册中遇到这样的标记。)

list.remove(x) 删除列表中值为 x 的第一个元素。如果没有这样的元素,就会返回一个错误。

list.index(x) 返回列表中第一个值为 x 的元素的索引。如果没有匹配的元素就会返回一个错误。

列表方法使得列表可以很方便的作为一个堆栈来使用,堆栈作为特定的数据结构,最先进入的元素最后一个被释放(后进先出)。用 append() 方法可以把一个元素添加到堆栈顶。用不指定索引的 pop() 方法可以把一个元素从堆栈顶释放出来。例如:

8.8 把列表当作队列使用

也可以把列表当做队列用,只是在队列里第一加入的元素,第一个取出来;但是拿列表用作这样的目的效率不高。在列表的最后添加或者弹出元素速度快,然而在列表里插入或者从头部弹出速度却不快(因为所有其他的元素都得一个一个地移动)。

>>> from collections import deque
>>> queue = deque(["Eric", "John", "Michael"])
>>> queue.append("Terry")           # Terry arrives
>>> queue.append("Graham")          # Graham arrives
>>> queue.popleft()                 # The first to arrive now leaves
'Eric'
>>> queue.popleft()                 # The second to arrive now leaves
'John'
>>> queue                           # Remaining queue in order of arrival
deque(['Michael', 'Terry', 'Graham'])

8.9 遍历技巧

在序列中遍历时,索引位置和对应值可以使用enumerate()函数同时得到:

>>> for i, v in enumerate(['tic', 'tac', 'toe']):
...     print(i, v)
...
0 tic
1 tac
2 toe

同时遍历两个或更多的序列,可以使用zip()组合

>>> questions = ['name', 'quest', 'favorite color']
>>> answers = ['lancelot', 'the holy grail', 'blue']
>>> for q, a in zip(questions, answers):
...     print('What is your {0}?  It is {1}.'.format(q, a))
...
What is your name?  It is lancelot.
What is your quest?  It is the holy grail.
What is your favorite color?  It is blue.

列表推导式的执行顺序:各语句之间是嵌套关系,左边第二个语句是最外层,依次往右进一层,左边第一条语句是最后一层。

for x in range(1,5)
    if x > 2
        for y in range(1,4)
            if y < 3
                x*y
matrix = [ [7, 2, 9, 4], [5, 6, 9, 8], [9, 10, 11, 12],]


relist1 = [row[i] for i in range(4) for row in matrix]
relist2 = [[row[i] for row in matrix] for i in range(4)]

print(relist1)
print(relist2)

输出:

[7, 5, 9, 2, 6, 10, 9, 9, 11, 4, 8, 12]
[[7, 5, 9], [2, 6, 10], [9, 9, 11], [4, 8, 12]]

9 模块

9.1 import语句

一个模块只会被导入一次,不管你执行了多少次import,这样可以防止导入模块被一遍又一遍地执行

9.2 __name__属性

一个模块被另一个程序第一次引入时,其主程序将运行。如果我们想在模块被引入时,模块中的某一程序块不执行,我们可以用__name__属性来使该程序块仅在该模块自身运行时执行。

#!/usr/bin/python3
# Filename: using_name.py

if __name__ == '__main__':
   print('程序自身在运行')
else:
   print('我来自另一模块')

运行输出如下:

$ python using_name.py
程序自身在运行
$ python
>>> import using_name
我来自另一模块
>>>

说明: 每个模块都有一个__name__属性,当其值是’main’时,表明该模块自身在运行,否则是被引入。
说明:namemain 底下是双下划线

dir()函数,内置的dir()可以找到模块内定义的所有名称,以一个字符串列表的形式返回

>>> import fibo
>>> dir(fibo)
['__name__', 'fib', 'fib2']

如果没有给定参数,那么dir()函数会罗列出当前定义的所有名称

9.3 包

包是一种管理Python模块命名空间的形式,采用“点模块名称”,比如一个模块的名称是A.B,那么他表示一个包A中的子模块B。就好像使用模块的时候,你不用担心不同模块之间的全局变量相互影响一样,采用点模块名称这种形式也不用担心不同库之间的模块重名的情况。

在导入一个包的时候,Python 会根据 sys.path 中的目录来寻找这个包中包含的子目录。目录只有包含一个叫做 init.py 的文件才会被认作是一个包,主要是为了避免一些滥俗的名字(比如叫做 string)不小心的影响搜索路径中的有效模块。最简单的情况,放一个空的 :file:init.py就可以了。当然这个文件中也可以包含一些初始化代码或者为(将在后面介绍的) __all__变量赋值。用户可以每次只导入一个包里面的特定模块,比如:

import sound.effects.echo

这将会导入子模块:sound.effects.echo。 他必须使用全名去访问:

sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)

还有一种导入子模块的方法是:

from sound.effects import echo

这同样会导入子模块: echo,并且他不需要那些冗长的前缀,所以他可以这样使用:

echo.echofilter(input, output, delay=0.7, atten=4)

类似的还可以直接导入一个函数或者变量。

注意当使用 from package import item 这种形式的时候,对应的 item 既可以是包里面的子模块(子包),或者包里面定义的其他名称,比如函数,类或者变量。import 语法会首先把 item 当作一个包定义的名称,如果没找到,再试图按照一个模块去导入。如果还没找到,抛出一个 :exc:ImportError 异常。反之,如果使用形如 import item.subitem.subsubitem 这种导入形式,除了最后一项,都必须是包,而最后一项则可以是模块或者是包,但是不可以是类,函数或者变量的名字。

9.4 从一个包中导入*

如果我们使用 from sound.effects import * 会发生什么呢?Python 会进入文件系统,找到这个包里面所有的子模块,然后一个一个的把它们都导入进来。但这个方法在 Windows 平台上工作的就不是非常好,因为 Windows 是一个不区分大小写的系统。在 Windows 平台上,我们无法确定一个叫做 ECHO.py 的文件导入为模块是 echo 还是 Echo,或者是 ECHO。为了解决这个问题,我们只需要提供一个精确包的索引。导入语句遵循如下规则:如果包定义文件 init.py 存在一个叫做 all 的列表变量,那么在使用 from package import * 的时候就把这个列表中的所有名字作为包内容导入。作为包的作者,可别忘了在更新包之后保证 all 也更新了啊。以下实例在 file:sounds/effects/init.py 中包含如下代码:

__all__ = ["echo", "surround", "reverse"]

这表示当你使用from sound.effects import *这种用法时,你只会导入包里面这三个子模块。如果 all 真的没有定义,那么使用from sound.effects import *这种语法的时候,就不会导入包 sound.effects 里的任何子模块。他只是把包sound.effects和它里面定义的所有内容导入进来(可能运行__init__.py里定义的初始化代码)。这会把 init.py 里面定义的所有名字导入进来。并且他不会破坏掉我们在这句话之前导入的所有明确指定的模块。看下这部分代码:

import sound.effects.echo
import sound.effects.surround
from sound.effects import *

这个例子中,在执行 from…import 前,包 sound.effects 中的 echo 和 surround 模块都被导入到当前的命名空间中了。(当然如果定义了 all 就更没问题了)。

如果在结构中包是一个子包(比如这个例子中对于包sound来说),而你又想导入兄弟包(同级别的包)你就得使用导入绝对的路径来导入。比如,如果模块sound.filters.vocoder 要使用包 sound.effects 中的模块 echo,你就要写成 from sound.effects import echo。

from . import echo
from .. import formats
from ..filters import equalizer

无论是隐式的还是显式的相对导入都是从当前模块开始的。主模块的名字永远是"main",一个Python应用程序的主模块,应当总是使用绝对路径引用。包还提供一个额外的属性__path__。这是一个目录列表,里面每一个包含的目录都有为这个包服务的__init__.py,你得在其他__init__.py被执行前定义哦。可以修改这个变量,用来影响包含在包里面的模块和子包。这个功能并不常用,一般用来扩展包里面的模块。

10 Python输入和输出

10.1 输出格式美化

str():函数返回一个用户易读的表达形式。
repr():产生一个解释器易读的表达形式。
>>> x = 10 * 3.25
>>> y = 200 * 200
>>> s = 'x 的值为: ' + repr(x) + ',  y 的值为:' + repr(y) + '...'
>>> print(s)
x 的值为: 32.5,  y 的值为:40000...
>>> #  repr() 函数可以转义字符串中的特殊字符
... hello = 'hello, runoob\n'
>>> hellos = repr(hello)
>>> print(hellos)
'hello, runoob\n'
>>> # repr() 的参数可以是 Python 的任何对象
... repr((x, y, ('Google', 'Runoob')))
"(32.5, 40000, ('Google', 'Runoob'))"

!a (使用 ascii()), !s (使用 str()) 和 !r (使用 repr()) 可以用于在格式化某个值之前对其进行转化:

>>> import math
>>> print('常量 PI 的值近似为: {}。'.format(math.pi))
常量 PI 的值近似为: 3.141592653589793>>> print('常量 PI 的值近似为: {!r}。'.format(math.pi))
常量 PI 的值近似为: 3.141592653589793

在 : 后传入一个整数, 可以保证该域至少有这么多的宽度。 用于美化表格时很有用。

>>> table = {'Google': 1, 'Runoob': 2, 'Taobao': 3}
>>> for name, number in table.items():
...     print('{0:10} ==> {1:10d}'.format(name, number))
...
Google     ==>          1
Runoob     ==>          2
Taobao     ==>          3

也可以通过在 table 变量前使用 ** 来实现相同的功能:

>>> table = {'Google': 1, 'Runoob': 2, 'Taobao': 3}
>>> print('Runoob: {Runoob:d}; Google: {Google:d}; Taobao: {Taobao:d}'.format(**table))
Runoob: 2; Google: 1; Taobao: 3

1、整数的输出

%o 八进制 oct%d 十进制 dec%x 十六进制 hex。

print('%o' % 20) # 八进制24
print('%d' % 20) # 十进制20
print('%x' % 24) # 十六进制18

2、浮点数输出

格式化符号说明备注 %f 保留小数点后面六位有效数字 float%e 保留小数点后面六位有效数字 %g 在保证六位有效数字的前提下,使用小数方式,否则使用科学计数法。

print('%f' % 1.11)         # 默认保留6位小数1.110000
print('%.1f' % 1.11)       # 取1位小数1.1
print('%e' % 1.11)         # 默认6位小数,用科学计数法1.110000e+00
print('%.3e' % 1.11)       # 取3位小数,用科学计数法1.110e+00
print('%g' % 1111.1111)    # 默认6位有效数字1111.11
print('%.7g' % 1111.1111)  # 取7位有效数字1111.111
print('%.2g' % 1111.1111)  # 取2位有效数字,自动转换为科学计数法1.1e+03

3、字符串输出

格式化符号说明备注 %s 字符串输出 string%10s 右对齐,占位符 10位%-10s 左对齐,占位符 10 位 %.2s 截取 2 位字符串 %10.2s10 位占位符,截取两位字符串。

print('%s' % 'hello world')       # 字符串输出hello world
print('%20s' % 'hello world')     # 右对齐,取20位,不够则补位         hello world
print('%-20s' % 'hello world')    # 左对齐,取20位,不够则补位hello world         
print('%.2s' % 'hello world')     # 取2位he
print('%10.2s' % 'hello world')   # 右对齐,取2位        he
print('%-10.2s' % 'hello world')  # 左对齐,取2位he

对齐方式:<:左对齐;>:右对齐;^:居中;=:在正负号(如果有的话)和数字之间填充,该对齐选项仅对数字类型有效。它可以输出类似 +0000120 这样的字符串。

>>> print("|",format("RUNOOB","*>30"),"|")    #左对齐
| ************************RUNOOB |
>>> print("|",format("RUNOOB","*^30"),"|")    #居中对齐
| ************RUNOOB************ |
>>> print("|",format("RUNOOB","*<30"),"|")    #右对齐
| RUNOOB************************ |

举个栗子

10.2 读和写文件

open()将会返回一个file对象,基本语法格式如下:

open(filename, mode)
# 模式
r	以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。
rb	以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。
r+	打开一个文件用于读写。文件指针将会放在文件的开头。
rb+	以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。
w	打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
wb	以二进制格式打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
w+	打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
wb+	以二进制格式打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。
a	打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
ab	以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
a+	打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。
ab+	以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写。

f.seek(),如果要改变文件指针当前的位置, 可以使用 f.seek(offset, from_what) 函数。from_what 的值, 如果是 0 表示开头, 如果是 1 表示当前位置, 2 表示文件的结尾,例如:seek(x,0) : 从起始位置即文件首行首字符开始移动 x 个字符;seek(x,1) : 表示从当前位置往后移动x个字符;seek(-x,2):表示从文件的结尾往前移动x个字符;from_what 值为默认为0,即文件开头。下面给出一个完整的例子:

>>> f = open('/tmp/foo.txt', 'rb+')
>>> f.write(b'0123456789abcdef')
16
>>> f.seek(5)     # 移动到文件的第六个字节
5
>>> f.read(1)
b'5'
>>> f.seek(-3, 2) # 移动到文件的倒数第三字节
13
>>> f.read(1)
b'd'

当处理一个文件对象时, 使用 with 关键字是非常好的方式。在结束后, 它会帮你正确的关闭文件。 而且写起来也比 try - finally 语句块要简短:

>>> with open('/tmp/foo.txt', 'r') as f:
...     read_data = f.read()
>>> f.closed
True

10.3 pickle模块

python的pickle模块实现了基本的数据序列和反序列化。通过pickle模块的序列化操作我们能够将程序中运行的对象信息保存到文件中去,永久存储。通过pickle模块的反序列化操作,我们能够从文件中创建上一次程序保存的对象。

基本接口:

pickle.dump(obj, file, [,protocol])

有了pickle这个对象,就能对file以读取的形式打开

x = pickle.load(file)

从file中读取一个字符串,并将它重构为原来的python对象

实例1

#!/usr/bin/python3
import pickle

# 使用pickle模块将数据对象保存到文件
data1 = {'a': [1, 2.0, 3, 4+6j],
         'b': ('string', u'Unicode string'),
         'c': None}

selfref_list = [1, 2, 3]
selfref_list.append(selfref_list)

output = open('data.pkl', 'wb')

# Pickle dictionary using protocol 0.
pickle.dump(data1, output)

# Pickle the list using the highest protocol available.
pickle.dump(selfref_list, output, -1)

output.close()

实例2

#!/usr/bin/python3
import pprint, pickle

#使用pickle模块从文件中重构python对象
pkl_file = open('data.pkl', 'rb')

data1 = pickle.load(pkl_file)
pprint.pprint(data1)

data2 = pickle.load(pkl_file)
pprint.pprint(data2)

pkl_file.close()

11 OS文件/目录方法

11.1 os.access(path,mode)

# path -- 要用来检测是否有访问权限的路径。
# mode -- mode为F_OK,测试存在的路径,或者它可以是包含R_OK, W_OK和X_OK或者R_OK, W_OK和X_OK其中之一或者更多。

# os.F_OK: 作为access()的mode参数,测试path是否存在。
# os.R_OK: 包含在access()的mode参数中 , 测试path是否可读。
# os.W_OK 包含在access()的mode参数中 , 测试path是否可写。
# os.X_OK 包含在access()的mode参数中 ,测试path是否可执行。

#!/usr/bin/python3

import os, sys

# 假定 /tmp/foo.txt 文件存在,并有读写权限

ret = os.access("/tmp/foo.txt", os.F_OK)
print ("F_OK - 返回值 %s"% ret)

ret = os.access("/tmp/foo.txt", os.R_OK)
print ("R_OK - 返回值 %s"% ret)

ret = os.access("/tmp/foo.txt", os.W_OK)
print ("W_OK - 返回值 %s"% ret)

ret = os.access("/tmp/foo.txt", os.X_OK)
print ("X_OK - 返回值 %s"% ret)

执行以上程序输出结果为:

F_OK - 返回值 True
R_OK - 返回值 True
W_OK - 返回值 True
X_OK - 返回值 False

11.2 os.chdir()

os.chdir() 方法用于改变当前工作目录到指定的路径。

os.chdir(path)
# path--要切换到的新路径

#!/usr/bin/python3

import os, sys

path = "/tmp"

# 查看当前工作目录
retval = os.getcwd()
print ("当前工作目录为 %s" % retval)

# 修改当前工作目录
os.chdir( path )

# 查看修改后的工作目录
retval = os.getcwd()

print ("目录修改成功 %s" % retval)

执行以上程序输出结果为:

当前工作目录为 /www
目录修改成功 /tmp
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值