Python基础教程(第三版)
💖写在前面:
🎈本文是根据[挪] Magnus Lie Hetland的《Python基础教程(第三版)》总结的部分个人笔记
🍕电子版资料想要的可以私信滴滴我哦
🍔如有错误欢迎交流指出!
第一章:基础知识
-
1、整除运算
\\
与取余运算%
规则: 整除运算结果向负无穷取整(支持负数整除,整除后离0更远);取余运算与整除运算相互对应。 -
2、python变量那些事: python的变量必须赋给初始值,即python变量没有默认值;变量名不能以数字开头。
-
3、二进制八进制十六进制:
0xAF
十六进制175,0o10
八进制8,0b10
二进制2。 -
4、
round()
的取数规则: 将浮点数取整为与之更接近的数,当距离两个整数一样近时取整到偶数(round(2.5)
结果为2)。 -
6、自动拼接字符串:
print('hello''world''!')
输出结果为helloworld!
-
7、字符串表示
str
和repr
:print(str("hello\nworld!")) # str以合理的方式将值转换为用户可以理解的字符串 print(repr("hello\nworld!")) # repr会返回对象的规范字符串表示形式 print(repr(1234),repr([1234])) # print函数输出对象的内容而非对象本身,例如打印字符串不打印引号 # 即:>>> repr("hello\nworld!")输出为'"hello\nworld!"' # hello # world! # 'hello\nworld!' # 1234 [1234]
-
8、长字符串和原始字符串:
(1)
""" str """
三引号表示的长字符串可包含单引号和双引号,不需要反斜杠转义(常规字符可用反斜杠横跨多行);(2)原始字符串是前缀为
r
的字符串,将屏蔽对字符串中反斜杠的特殊处理,将每个字符都保持原样。**一个例外是,引号需要像通常一样进行转义,这样意味着用于转义的反斜杠也将包含到最后的字符串中。**如:r'Let\'s go'
原始字符串和
repr(object)
不同,前者是屏蔽特殊处理的字符串,后者则是返回一个对象的字符串格式;相同的地方是不能以\
结尾,因为引号会被转义。 -
9、Unicode、bytes和bytearray:
1、Unicode:每个Unicode字符都用一个码点(codepoint)来表示,码点是Unicode标准给每个字符指定的数字;有一种指定的Unicode字符通用机制:使用16位或32位的十六进制字面量(分别加上前缀
\u
和\U
)或者使用字符的Unicode名称(\N{name}
)UTF-8、UTF-16、UTF-32都是将码点转换为程序数据的转换格式
2、bytes与bytearray对象:将字符串通过
str.encode()
函数按照一定的编码规则(默认为UTF-8)生成一个以b
为前缀的不可变字节序列,该不可变字节序列即为bytes对象;bytearray则是可变字节序列,想要对其中的字符进行替换时必须指定为0~255的值str1 = "hellox\a" print(str1.encode) # 该种情况不会报错,注意区别 print(str1.encode()) # <built-in method encode of str object at 0x00000189FBF62730> # b'hellox\x07' # bytes对象中的每一个字符类型均为int:type(str1.encode()[0]) == int # 对应的int值即为一定编码规则下的对应值 x = bytearray(b'hello!') x[1] = ord('u') # 等同于ord(b'u') print(x) # ord()函数接收一个字符作为参数,返回对应的Unicode码点 # 与chr()函数(对于八位ascii字符)或unichr()函数(对于Unicode对象)相对应 # python3不再支持unichr()改为chr()
python中数据转为二进制后非以二进制形式存储,而是以bytes类型存储:
字符串中的字符若在ascii码范围内时,在bytes中不变;在范围之外时储存形式为字符的码点在对应规则转化下的编码格式。例:Unicode/UCS-4(十六进制) 字节数 UTF-8编码格式(二进制) 000000-00007F 1 0xxxxxxx 000080-0007FF 2 110xxxxx 10xxxxxx 000800-00FFFF 3 1110xxxx 10xxxxxx 10xxxxxx 010000-10FFFF 4 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx # Π在Unicode标准中的码点为\u03c0(在第二层,两个字节表示) # 对于ascii编码,当不在range(128)时会用前128个字符对Π进行编码,编码结果为Unicde码点 # 根据上方的对应方式翻译\u030为二进制码:1100 1111 1000 0000 >>'Π'.encode() b'\xcf\x80' # 该二位十六进制码对应的二进制为 1100 1111 1000 0000
-
补充:
int(x,base = 10)
进行字符串强制转换时,会将x表示的字符串以base进制转化为10进制输出AZ对应ASCII码为6590,az为97122
附:x为小数时不能有base参数,否则将报错
TypeError: int() can't convert non-string with explicit base
函数 | 描述 |
---|---|
math.ceil(number) | 以浮点数的方式返回向上取整的结果 |
math.floor(number) | 以浮点数的方式返回向下取整的结果 |
math.sqrt(number) | 返回平方根;不能用于负数 |
cmath.sqrt(number) | 返回平方根;能用于负数 |
cmath
是用于处理复数的模块,不同库的同名函数注意调用时区分
第二章:列表和元组
Part 1:列表
-
**1、关于序列的几种操作:**索引、迭代、切片、相加、相乘和成员资格审查
对序列进行迭代意味着对其中的每一个元素都执行特定的操作
-
**2、切片注意:**步长不能为0,步长为负数时表示从右向左提取元素(起点总是由步长的正负决定);切片时注意起点与终点的索引大小关系
-
3、初始化指定长度的列表:
ls = [None]*n
定义一个长为n的空列表 -
4、列表操作(部分):
(1)删除指定元素:
del list1[n]
删除list1中索引为n的元素;(2)给切片赋值:(该操作可以改变切片的长度)
name = list('Perl') name[1:] = 'ython' # 输出:['P', 'y', 't', 'h', 'o', 'n'] # 标准做法为将赋值字符串放入[]内,不放入时若为切片则发生隐式强制转换 # 即若name = 'ython' 则type(name) == str;上例中仍为list
切片操作也能插入新的元素:
name = list('Perl') name[1:1] = 'ython' # 输出:['P', 'y', 't', 'h', 'o', 'n', 'e', 'r', 'l']
-
5、列表方法(部分):
(1)
list.clear()
:方法clear就地清空列表的内容,返回值为None;(2)
list.copy()
:方法copy复制列表,常规赋值只是将另一个名称关联到列表;list.copy()
等效于list()
和list[:]
(3)
list.count(value)
:方法count计算指定元素在列表中出现的次数并返回;(4)
append(object)
和extend(object)
:extend()
可以将多个元素放入列表,要求放入的元素作为一个序列传入函数,序列中的每个元素会依次加入列表中ls = [1] ls.append('2');ls.append((3,));ls.append({4,5}) # 字符串、元组、集合 print(ls) # 输出:[1, '2', (3,), {4, 5}] # append方法只能加入一个任意对象,该对象作为整体加在原列表最后 ls.extend('[123],123') ls.extend([1,2,3]) ls.extend((1,2,'2')) ls.extend({20,2,3}) print(ls) # [1, '2', (3,), {4, 5}, '[', '1', '2', '3', ']', ',', '1', '2', '3', 1, 2, 3, 1, 2, '2', 2, 3, 20]
(5)
list.index(value)
:方法index在列表中查找指定值第一次出现的索引,找不到时会报异常(6)
list.insert(index, object)
:方法insert用于将一个对象插入列表的指定位置ls = [ 1, 2, 3, 4, 5];print(ls.insert( 1, 123), ls) # None [1, 123, 2, 3, 4, 5]
(7)
list.pop(index = -1)
:方法pop从列表中删除一个元素(默认为最后一个),并且返回这个元素pop是唯一既修改列表又返回非None值的列表方法,可以与append和insert相互结合完成对栈和队列的操作
(8)
list.remove(value)
:方法remove用于删除第一个为指定值value的元素,删除非列表内的值会报错(9)
list.reserve()
:方法reverse按照相反的顺序排列列表中的元素reverse只能用于列表,
reversed(seq)
函数返回一个反转迭代器,可以对序列进行转换(10)
list.sort()
和sorted(list)
:均用于列表的排序,前者类内方法为就地排序,直接对原列表修改;后者是一个返回排序后列表的函数,原列表不变(可以用于任何序列,但总返回一个列表)sort方法和sorted函数均有两个参数key和reverse:key可以为函数,赋给列表每个元素以权值,然后列表按照权值大小排序;reverse默认为False,从大到小排序,True时从小到大排序
【注意两者的调用区别】:sorted不是列表方法
Part 2:元组
-
**1、元组:**元组也是序列,与列表的唯一差别时元组不能被修改,单元素元组末尾必须加逗号
-
**2、元组的作用:**元组作为映射中的键(以及集合的成员),而列表不行;Python中部分内置函数和方法返回元组
集合内元素不可变,因此列表不能为集合的成员
-
补充:
函数 描述 len(seq)
返回序列的长度 max(args)
返回序列或一组参数中的最大值(可以为多个非序列的参数) min(args)
返回序列或一组参数中的最小值(同上)
第三章:字符串和字典
Part 1:设置字符串格式
-
format方法:将变量值填入字符串内的花括号内部
>>> "{foo} {1} {bar} {0}".format(1,2,bar = 3, foo = 4)# 位置参数 '4 2 3 1' >>> "{}"
-
设置字符串格式的基本思想就是对字符串调用方法format,并提供要设置其格式的值。每个值都要被插入字符串中,以替换用花括号括起来的替换字段,若要保留花括号则使用两个花括号来指定
>>> "{{ceci n'est pas une replacement field }}".format() "{ceci n'est pas une replacement field }" # 没有要插入的值,所以保持原状
替换字段由以下部分组成:
-
字段名:索引或者标识符,指出要设置哪个值的格式并使用结果来替换该字段。
-
转换标志:跟在感叹号后面的单个字符,当前支持的字符包括r(repr)、a(ascii)、s(str)。如果指定了转换标志,将不再使用对象本身的格式设置机制,而是使用指定的函数将对象转换为字符串,再做进一步的格式设置。
-
格式说明符:跟在冒号后面的表达式,这种表达式使用微型格式指定语言,指定详细的最终输出格式。
-
-
基本转换:
>>> print("{pi!s} {pi!r} {pi!a}".format(pi = 'Π')) Π 'Π' '\u03c0'
以上三个转换标志指定分别使用str、repr、ascii进行转换:str即
str()
进行强转,repr返回指定对象的字符串表示形式,其中ascii函数创建只包含ASCII字符的表示。 同时可以使用格式说明符来限制输出的格式(即冒号后面)
'{num:.2f}'.format(num = 1.2345)
将浮点类型限制到两位。字符串格式设置中的类型说明符
类型 | 含义 |
---|---|
b | 将整数表示为二进制数 |
c | 将整数解读为Unicode码点 |
d | 将整数视为十进制数进行处理,是整数默认的说明符 |
e | 使用科学表示法来表示小数,用e来表示指数 |
E | 与e相同,但是用E来表示指数 |
f | 将小数表示为定点数(默认为六位小数) |
F | 与f相同,但是对于特殊值(nan和inf无穷大)使用大写表示(NAN和INF) |
g | 自动在定点表示法和科学表示法之间做出选择。默认用于小数的说明符,但在默认情况下至少有1位小数 |
G | 与g相同,但是用大写来表示指数和特殊值 |
n | 与g相同,但是插入随区域而异的数字分隔符 |
o | 将整数表示为八进制数 |
s | 保持字符串的格式不变 |
x | 将整数表示为十六进制数并使用小写字母(默认不带前缀) |
X | 与x相同但使用大写字母 |
% | 将数字表示为百分比值(乘以100,按说明符f设置格式,再在后面加上%) |
-
格式化标识符:
{num:<填充字符><对齐><宽度><千位分隔符>.<精度><类型说明符>}.format(num = )
其中千位分隔符为,
(不能与类型说明符n同时出现)1、指定填充字符时必须指定对齐方式,
'{num:$10.2f}'.format(num = 3.1415)
报错# ValueError: Invalid format specifier
2、指定宽度后,字符串默认为左对齐,数的默认对齐方式是右对齐;变量为字符串时没有后三个标识符(千分位、精度和类型说明符)
print('{num:20,.3f}'.format(num = 987654321)) # 设置宽度为20、保留三位小数且使用千位分隔符 # 987,654,321.000 print('{num:$<10.2f}\n{num:$^10.2f}\n{num:$>10.2f}'.format(num = 3.1415)) # 填充为'$'宽度为10(不指定填充时默认为空格),保留两位小数,从左到右依次为左对齐、居中、和右对齐 #3.14$$$$$$ #$$$3.14$$$ #$$$$$$3.14 # 其他符号:‘=’、‘+’、‘#’(最后一种略) from math import pi print('{:+10.2f}\n{:=10.2f}'.format(pi,-pi)) # 符号说明符号+,放在对齐说明符之后 # =指定将填充字符放在符号和数字之间,与对齐符同时出现没有效果 # +3.14 #- 3.14
Part 2:字符串方法
-
1、
str.center(width, char = ' ')
:方法center通过在两边添加填充字符(默认为空格来让字符串居中) -
2、
str.find(str1, start, end)
:方法find在字符串中查找子串,如果找到就返回子串的第一个字符的索引,否则返回-1(可以指定查找的起点和终点,包含起点不包含终点) -
3、
str.join(seq)
:join方法将分隔符str
插入元素均为字符串的序列seq
中,与split相反,用于合并序列的元素split(sep,num)
函数接收两个参数,第一个参数是分隔符,第二个参数表示分割次数,在分隔符处分开并返回一个列表(分隔符会被删除,不在列表中) -
4、
str.lower()
:方法lower返回字符串的小写版本,可以用于屏蔽大小写区分 -
5、
str.replace(a,b)
:用字符串b替换字符串a并返回替换后的字符串(replace a with b),原来的字符串不变 -
6、
str.strip()
:方法strip将字符串开头和末尾的空白删除,并返回删除后的结果,原来的字符串不变(可以在括号内指定删除哪些字符) -
7、
str.translate()
:方法translate与replace一样替换字符串的特定部分,但是不同的是它只能进行单字符替换,优势在于能够同时替换多个字符,效率比replace更高# 使用translate()之前必须先创建一个转换表,转换表指出不同Unicode码点之间的转换关系 # 对str类型调用maketrans()方法生成转换表,该方法接收两个长度相等的字符串str1和str2,指定str1中的每一个字符都替换为str2中的相应字符,第三个参数可以指定删除哪些字母 table = str.maketrans('cs','kz','w')# cs替换为kz并删除w print(table) # {115:112,99:107,119:None} 码点的映射(创建了对应的映射,删除后对应为None) print('i want to play cs'.translate(table)) # i ant to play kz
Part 3:字典及其方法
-
将字符串格式设置应用于字典(format_map):
phoneBook = {'lhz':1234,'zhl':4321} phoneBook1 = {'lhz':1234,123 :4321} phoneBook2 = {'lhz':1234,(1,2,3):4321} print('lhz\'s phone number is {lhz}'.format_map(phoneBook)) # 仅当键为str时,在大括号内放入键的内容(上述phoneBook1和2均无法访问第二对键值) # lhz's phone number is 1234
dict.keys()
来获取字典的键,返回值为class_keys类的对象,dict.values()
同理 -
1、
dict.clear()
:方法clear可以清除所有的字典项,返回值为None -
2、
dict.copy()
:浅层复制,替换不影响,修改会影响(修改对应值为列表的情况)deepcopy()
:将dict作为参数传入进行深层复制(注意与copy的调用方式不同),开辟新的空间并复制值,修改和替换都不会影响,不过需要from copy import deepcopy
-
3、
dict.fromkeys([key1,key2...] , value = None)
:创建一个值相同的字典,可以指定特定的值,默认为None -
4、
dict.get(key, ret = None)
:方法get为访问字典提供了宽松的环境:访问键为key的值,若存在则返回对应的值;若不存在则返回ret(可指定特定的值,默认为None)dict.setdefault(key, ret = None)
:与get类似,获取指定键的关联值,若不存在则返回ret,并且将ret作为值插入字典d = {} print(d.setdefault('lhz',1234)) print(d) # {'lhz': 1234}weg print(d.setdefault('lhz',None)) # 1234 若key有对应的值,则返回值
-
5、
dict.items()
:items方法返回一个包含所有字典项的列表,表中每个元素都为(key,value)
的形式,排列顺序不定返回值属于一种名为字典视图的特殊类型,字典视图可以用于迭代而且可以确定其长度以及进行成员资格审查。字典视图的一个优点是不复制,始终指向字典,随字典改变而变化
-
6、
dict.pop(key)
:获取指定的键值对,并将其从字典中删除 -
7、
dict.popitem()
:从字典中弹出一个字典项,并以元组的形式返回,默认为字典项的最后一个(Python3.8.6,python 3.7以前为删除一个随机项) -
8、
dict.update()
:使用一个字典中的项更新另一个字典,对于参数提供的字典,将 其添加到当前字典中。若字典包含相同键相同的项,则替换它(返回值为None)
第四章:条件、循环及其他语句
Part 1:再谈print和import
-
1、合并文本的变量值:
print(str1 + str2)
直接在print内运算 -
2、import的几种方式:
import somemodule from somemodule import fun1,fun2,fun3 from somemodule import * # 导入模块中的所有内容 # 可以给导入的模块起别名 >>> import math as hello >>> hello.sqrt(4) 2.0
Part 2:赋值语句
-
1、序列解包:将一个序列解包,并将得到的值储存到一系列变量中
# 实现并行赋值 >>> x,y,z = 1,2,3 >>> print(x,y,z) 1 2 3 # 还能交换多个变量的值 >>> x,y = y,x >>>print(x,y,z) 2 1 3
实际上上面的操作是对元组的解包,函数可以通过返回一个元组来实现返回多个值的操作,不过打包序列包含的元素个数必须要和等号左边的目标个数相同
# 也可以用 * 来收集多余的值,这样无需保证等号左右两边元素个数相同 # 收集的元素会以列表的形式放在*后的变量名中 >>> a,b,*rest = [1,2,3,4,5] >>> rest [3,4,5] # 也可以将*放在其他位置 >>> a,*rest,b = [1,2,3,4,5] >>> a,b,rest (1, 5, [2, 3, 4])
-
2、链式赋值:将多个变量关联到同一个值
x = y =fun1()
Part 3:条件语句与布尔值
-
1、用作布尔表达式时,下面第一行的值都将被解释器视为假,而除此之外的所有值都被视为真
# False None 0 "" () {} [] # 要注意,被视为假和是假本身是不一样的 >>> print(False != (),False != 0,False != {},False != [],False != "") True False True True True # 输出结果可知,0 == False,其他的对象仅在判断时被视为False
题外话:
print(repr(input()),end='#')
直接回车输出结果为''#
,即不输入便为空字符串 -
2、
startswith()
和endswith()
:函数判断一个文本是否以某个(或者几个)字符开始(或者结尾),结果返回bool值 -
3、三目运算符python版:
x = value1 if expression else value2
若表达式成立则x为value1,否则为value2 -
4、相同与相等:
x is y
来判断x与y是否为同一个对象;x == y
来判断是否相等注意相同不等于相等,
lis1 = [1,2,3];lis2 = [1,2,3]
此时lis1和lis2相等不相同 -
5、链式比较:可以同时使用多个比较运算符,如
0 < age < 100
短路逻辑:当涉及布尔运算符时,python只做必要的运算,如
x or y
先判断x过后,部分情况下能由x直接判断出结果,此时不会再检查yname = input("please enter your name:") or '<unknown>'
若没有输入则名字内容为unknown -
6、断言:让程序出现错误条件时立即崩溃
>>> age = 10 >>> assert 0 < age < 100 # 正常运行 >>> age = -1 >>> assert 0 < age < 100 # 引发异常 Traceback (most recent call last): File "c:\", line 127, in <module> assert 0 < age < 100 AssertionError >>> age = -1 # 还可以在条件后面添加一个字符串,对断言做出说明 >>> assert 0 < age < 100,'the age must be realistic' Traceback (most recent call last): File "c:\", line 127, in <module> assert 0 < age < 100 AssertionError:'the age must be realistic'
Part 4:循环与迭代
-
1、可迭代对象是可使用for循环进行遍历的对象,range函数返回一个可迭代对象
-
2、并行迭代
zip
:一个内置函数,将多个个序列缝合起来,并返回一个由元组组成的序列。返回值是一个适合迭代的对象,要查看内容可以进行强制转换lis1 = ['123','234','345'] lis2 = [123,234,345] print(list(zip(lis1,lis2))) # [('123', 123), ('234', 234), ('345', 345)] # 也能缝合长度不同的序列,缝合后的长度取决于两者中最短的一个 print(list(zip(range(5),range(100000)))) # [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)]
-
3、迭代时获取索引
enumerate
:内置函数,迭代对象序列的同时获取当前对象的索引,返回一个可迭代的枚举对象# 替换字符串列表中所有包含子串'xxx'的字符串 for index,string in enumerate(string): if 'xxx' in string: string[index] = '[censored]' # 替换
-
4、反向迭代和排序后再迭代
reversed、sorted
:sorted排序后迭代一个序列或可迭代对象,返回一个列表;reversed反向迭代一个序列或可迭代对象,返回一个可迭代的reversed对象print(reversed(['a','B','c'])) print(list(reversed(['a','B','c']))) # <list_reverseiterator object at 0x0000024E8AB47EB0> # ['c', 'B', 'a']
-
5、循环中的else:for循环过程中没有发生break,则执行紧随其后的else语句,可以用来判断循环是否提前结束
for i in range(5): pass else: print("hello world!")
循环举例:(常见技巧)
while True: word = input() if not word:break # do something here print('The word is:'word)
-
推导式拓展:
# 推导式可以支持两个变量和两个for list1 = [(x,y) for x in range(3) for y in range(3)] # [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)] # 也可以用来生成字典 dict1 = {i:'{}^2 is {}'.format(i,i**2) for i in range(3)} print(dict1) # {0: '0^2 is 0', 1: '1^2 is 1', 2: '2^2 is 4'}
Part 5:del、exec与eval
-
1、del语句:对于不再使用的对象,python通常会将其删除。如下,当lis1和lis2都改变指向时,列表
[1,2,3,4]
就漂浮在计算机中,没有任何名称与之关联,无法再使用,python解释器会将其删除,该过程被称为垃圾收集。另一种方法,也可以通过del语句进行删除。lis1 = [1,2,3,4];lis2 = lis1 lis1 = None;print(lis1,lis2) lis2 = None;print(lis1,lis2) # None [1, 2, 3, 4] # None None dic1 = {'123':123} del dic1;print(dic1) # Traceback (most recent call last): # File "C:\", line 149, in <module> # del dic1;print(dic1) # NameError: name 'dic1' is not defined
-
2、exec语句:将字符串最为代码执行,可以为其指定一个字典作为专门的命名空间,防止影响原本的命名空间。
exec("print('hello world!')") # hello world! from math import sqrt exec('sqrt = 1') # 命名空间相互影响导致sqrt无法正常调用 sqrt(4) # Traceback (most recent call last): # File "D:\学习资料\Python\test.py", line 155, in <module> # sqrt(4) # TypeError: 'int' object is not callable from math import sqrt scope = {} exec('sqrt = 1',scope) print(sqrt(4),scope['sqrt']) # 2.0 1
-
3、eval语句:计算字符串表示的python表达式的值并返回元组,可以据此创建一个计算器,也可以指定一个字典作为其专门的命名空间
eval(input()) # 6+18*2 # 42
第五章:函数
Part 1:自定义函数
-
1、计算斐波那契数列的一种方法:
fibs = [0,1];num = int(input()) for i in range(num-2): fibs.append(fibs[-2]+fibs[-1]) # 很巧妙
-
2、一般而言,要判断某个对象是否可以调用,可以使用内置函数
callable()
-
3、给函数编写文档:放在函数开头的字符串叫做文档字符串,将作为函数的一部分储存起来
def fun(): 'function doc' pass
-
4、所有函数都有返回值,若不人为定义则返回None
不要让默认的返回值带来麻烦,如果在if语句中返回值,务必确保其他分支也有返回值,否则将引起意外的问题
-
5、lambda函数:
lambda 参数表: 表达式
,函数返回值为表达式的值
Part 2:函数的参数
从左到右参数位置:位置参数 - > 关键字参数
-
1、收集参数:
*
会收集位置参数(与位置有关的参数),收集后放入元组中;**
会收集关键字参数,收集后放入字典,是指定类型的参数而不是指定类型,字典也可能是位置参数! -
2、分配参数:
*
将元组进行解包,解包为位置参数;**
将字典进行解包,解包为关键字参数只有在定义函数(设计参数)和调用函数(传入参数)时,星号才能起作用。传参时将字典或元组解包为对应类型的参数,接收参数时将多个参数打包为元组或者字典,无需过多考虑参数个数的问题。
Part 3:作用域
-
1、看不见的字典,叫做命名空间或作用域,除了全局作用域以外,每一个函数都有一个局部作用域
可以通过vars()函数来访问作用域,函数返回一个字典,可以以变量名为键访问变量对应的值
一般而言,不能修改vars()返回的字典,按照官方文本所说,这样的结果是不可预测的
-
2、globals访问全局变量,函数
globals()
类似于vars()返回全局作用域(6包含全局变量的字典),同理locals返回一个包含局部变量的字典。重新关联全局变量时直接用globalx = 1 def fun(): global x x += 1 print(x) fun() # 2
-
3、作用域嵌套:闭包,函数嵌套,外层函数返回内层函数,返回的函数能够访问其定义所在的作用域
def outer(x): num = 0 def inner(y): nonlocal num # 注释掉此行和下一行输出结果为num is 0 num = 99 print(f"num is {num}") return x**y return inner print(outer(2)(3)) # num is 99 # 8
通常,不能给外部作用域的变量赋值,如果一定这么做,可以使用nonlocal,与global用法相同,使得可以给外部作用域(非全局作用域)内的变量赋值
-
其他函数:
函数 描述 map(func,seq[,seq,....])
对序列中的所有元素执行函数 filter(func,seq)
返回一个列表,其中包含对其执行函数时结果为真的所有元素 reduce(func,seq[,initial])
等价于 func(func(func(seq[0],seq[1]),seq[2]),...)
apply(func[,args[,kwargs]])
调用函数,同时提供参数给函数 sum(iterable[,start]
对可迭代对象进行求和后加上start,start为相加的数,默认为0 前面两个函数都能用推导式进行替换,所以可以尽量使用推导式,增加程序可读性
第六章:抽象
# 类的定义
class ClassName(object): # 所有类都隐式继承了object或者将__metaclass__设置为type
"""docstring for ClassName"""
def __init__(self, arg):
super(ClassName, self).__init__()
self.arg = arg
-
1、class语句将会创建独立的命名空间
-
2、self总是指向对象本身,所以习惯上将其命名为self
-
3、若foo是一个Person实例,可以将
foo.greet()
视为Person.greet(foo)
-
4、私有属性名称以两个下划线开头,私有属性不能从对象外部访问,只能通过存取器方法(如get_name和set_name)
python内部处理方法:在类定义中,对所有以两个下划线开头的名称都进行转换,即在开头加上一个下划线和一个类名,如
Person._Person__privateMethod()
。可以用这样的处理机制从类外访问私有方法,但是并不应该这么做。 -
5、如果不希望名称被修改,且做出不要从外部修改属性或方法的标志,可以用一个下划线开头。如
from module import *
不会导以一个下划线开头的名称 -
6、内置方法
issubclass
与isinstance
:前者确定一个类是否是另一个类的子类,后者确定对象是否是特定类的实例class A: pass class B(A): pass a = A();b = B() print(issubclass(B,A),isinstance(a,B),B.__bases__) # True False (<class '__main__.A'>,)
如果有一个类,想知道他的基类,可以访问其特殊属性
class.__bases__
,想要知道一个对象属于哪个类,可以用属性object.__class__
-
7、多继承时必须注意基类的排列顺序,位于前面的类方法将会覆盖位于后面的类的方法
多个超类的超类相同时,查找特定方法或属性时的访问超类的顺序称为方法解析顺序(MRO)
-
8、抽象类:抽象类即包含抽象方法(在子类中必须实现的方法)的类,其最重要的特征是不能实例化
from abc import ABC,abstractmethod class Talker(ABC): @abstractmethod # 将talk方法标记为抽象的 def talk(self): pass class Knigget(Talker): # 该子类没有重写talk进行实现,所以该类也是抽象类,不能实例化 pass print(Knigget()) # Traceback (most recent call last): # File "C:\", line 199, in <module> # print(Knigget()) # TypeError: Can't instantiate abstract class Knigget with abstract methods talk class Knigget1(Talker): def talk(self): print('hello') # 对talk实现以后,可以对该类进行实例化,且实例化对象也是抽象类的对象 print(isinstance(Knigget(),Talker)) # True
可以通过
Talker.register(class1)
将其他类注册为Talker,使得所有的class1对象都被视为Talker对象,能够通过isinstance
和issubclass
的检查,但是class1内部不存在对应的方法,因此容易出现各种问题。所以使用isinstance
和issubclass
-
9、基本概念总结:
-
其他函数:
函数 描述 callable(object)
判断对象是否是可以调用的(如是否是函数或者方法) getattr(object,name[,default])
获取属性的值,还可以提供默认的返回值 hasattr(object,name)
确定对象是否有指定的属性 random.choice(sequence)
从一个非空序列中随机地选择一个元素 setattr(object,name,value)
将对象的指定属性设置为指定的值 # getattr函数的使用方法 >>>class A(object): ... bar = 1 ... >>> a = A() >>> getattr(a, 'bar') # 获取属性 bar 值 1 >>> getattr(a, 'bar2') # 属性 bar2 不存在,触发异常(不存在属性且未指定默认值) Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'A' object has no attribute 'bar2' >>> getattr(a, 'bar2', 3) # 属性 bar2 不存在,但设置了默认值 3
第七章:异常
-
1、Python使用异常对象来表示异常状态,并在遇到错误时引发异常。异常对象未被处理或捕获时,程序将会终止并显示一条错误信息(traceback)
-
2、raise语句:用来引发异常,并将一个类(必须是Exception的子类)或实例作为参数。以类作为参数时,将自动创建一个实例。
>>> raise Exception("hyperdrive overload") Traceback (most recent call last): File "<pyshell#0>", line 1, in <module> raise exception("hyperdrive overload") Exception: hyperdrive overload
-
3、自定义异常:像创建类一样,直接或间接地继承Exception(这意味着从任何内置异常类派生都可以),如下
class SomeCustomException(Exception): pass
-
4、捕获异常:try/except语句,引发异常之后跳转到except语句,执行语句内的内容,不再报错。但是可以通过不带参数的raise重新引发异常
try: x,y = map(int,input().split(' '))# 输入10,0 print(x/y) except ZeroDivisionError: print("The second number can't be zero!") raise # 捕获异常之后还能重新引发,此时不需要提供任何参数 # 举例应用 class MuffledCalculator: muffled = False def calc(self, expr): try: return eval(expr) except ZeroDivisionError: if self.muffled: print('Division by zero is illegal') # 打开抑制功能不引发异常 else: raise # 关闭抑制功能重新引发异常 >>> calculator.calc('10 / 0') # 关闭了抑制功能 Traceback (most recent call last): File "<stdin>", line 1, in ? File "MuffledCalculator.py", line 6, in calc return eval(expr) File "<string>", line 0, in ? ZeroDivisionError: integer division or modulo by zero >>> calculator.muffled = True # 打开抑制功能 >>> calculator.calc('10 / 0') Division by zero is illegal
-
5、处理异常时引发另一个异常:可以使用
raise ... from ...
来提供上下文,也可以用None来禁用上下文try: x,y = map(int,input().split(' '))# 输入10,0 # if y == 0:raise NoneError print(x/y) except ZeroDivisionError: raise NameError('there is an ERROR') #from None ''' Traceback (most recent call last): File "D:/学习资料/Python/test.py", line 229, in <module> print(x/y) ZeroDivisionError: division by zero During handling of the above exception, another exception occurred: 在处理上述异常时,又发生了另一个异常: Traceback (most recent call last): File "D:/学习资料/Python/test.py", line 231, in <module> raise NameError('there is an ERROR') #from None NameError: there is an ERROR ''' # 若上面为raise NameError('there is an ERROR') from ZeroDivisionError则输出结果如下 ''' ZeroDivisionError # 上下文消息消失 The above exception was the direct cause of the following exception: 上述异常是下列异常的直接原因: Traceback (most recent call last): File "D:/学习资料/Python/test.py", line 231, in <module> raise NameError('there is an ERROR') from ZeroDivisionError NameError: there is an ERROR '''
如上例:使用
raise A from B
时,python会把A异常的__cause__
属性设置为B异常,同时设置A的__supress_context__
为True,从而忽略B的__context__
属性,不打印B的异常上下文信息 -
6、多个except语句、else和finally:可以创建多个except语句捕捉不同的异常,从而做出不同反应
while True: try: ... except Error1: ... except (Error2,Error3) as e: # 括号非常重要,否则会引起一些错误,可以打印e来输出异常信息 ... except: # 不属于上述三种异常的将会被此段代码捕获 ... else: # 没有出现异常时,执行else(有except语句时才能有else) break # 没有发生异常时跳出循环 finally: # 与try语句配套,无论try中有无异常都将执行finally ...
-
7、异常与函数:异常在函数中发生之后会从调用处回溯到异常发生的位置
如果不处理函数中引发的异常,它将向上传播到调用函数的地方。如果在那里也未得到处理,异常将继续传播,直至到达主程序(全局作用域)。如果主程序 中也没有异常处理程序,程序将终止并显示栈跟踪消息。
-
8、如果只想发出警告,可以使用模块
warnings
中的函数warn
>>> from warnings import warn >>> warn("i've got a bad feeling about this.") Warning (from warnings module): File "<pyshell>", line 1 UserWarning: i've got a bad feeling about this. # 警告只显示一次 # 可以使用warnings中的函数filter函数抑制发出的警告并指定采取的措施:"error"或"ignore" >>> from warnings import filterwarnings >>> filterwarnings("ignore") >>> warn("Something wrong") # 无事发生 >>> filterwarnings("error") >>> warn("somthing error") # 引发异常 Traceback (most recent call last): File "<pyshell>", line 1, in <module> warn('something error') UserWarning: something error # 也可以指定引发异常的类别(即警告类别),但是必须是Warning的子类 # 使用“error”将警告转换为错误时,将使用指定的异常 >>> filterwarnings("error") >>> warn("This function is really old...", DeprecationWarning) # 指定异常类型与警告信息 Traceback (most recent call last): File "<stdin>", line 1, in <module> DeprecationWarning: This function is really old... # 利用filterwarnings可以过滤特定类型的警告(category n.类型) >>> filterwarnings("ignore", category=DeprecationWarning) >>> warn("Another deprecation warning.", DeprecationWarning)# 指定异常类型但被过滤 >>> warn("Something else.") # 未指定异常类型,使用默认的异常并发出警告信息 Traceback (most recent call last): File "<stdin>", line 1, in <module> UserWarning: Something else.
-
总结:python相对与if/else来说更偏向于使用try/except,直接去做,有问题再处理,而不是预先做大量的检查
函数 描述 warnings.filterwarnings(action,category=Warning,...)
过滤警告,action为"error"或"ignore" warnings.warn(message,category=None)
发出警告,message为警告信息
第八章:特殊方法、特性和迭代器
Part 1:构造函数
-
1、构造函数
__init__
不同于普通函数,会在对象创建后自动调用,用于初始化新建对象的状态构造函数对应的是析构函数
__del__
,该方法在对象被销毁(作为垃圾被收集)前调用由于无法知道准确的调用时间,建议尽可能不要使用
__del__
-
2、每个类都有一个或多个超类,并从它们那里继承行为。对类B的实例调用方法或属性时,如果找不到该方法或属性,将在其超类A中查找。子类重写超类方法,则优先调用重写后的方法。
尝试重写构造函数时,必须调用超类的构造函数,否则可能无法正确地初始化对象
-
确保超类执行基本的初始化有以下两种方法:
1、调用未关联的超类构造函数(旧版python):对实例调用方法时,方法的参数self将自动关联到实例(称为关联的方法),未关联方法即直接通过类名调用类方法
class A: def __init__(self): self.name = 'class A' def myprint(self): print(self.name) class B(A): def __init__(self): A.__init__(self)# 此行注释掉程序报错 # 此时超类A的构造函数未关联对象,调用时将对参数属性进行初始化 self.age = '20' b = B() b.myprint() # class A
2、使用函数super(新版python):调用super函数时,将当前类和当前对象作为参数,对其返回的对象调用方法时,调用的是超类的方法而不是当前类的方法
# 1中代码第8行换为以下代码 super(B,self).__init__()# super的括号内可以不提供任何参数
super函数返回的实际上时一个super对象,这个对象负责执行方法解析。当访问它的属性时,它将在所有的超类中进行查找,直到找到指定的属性或引发
AttributeError
异常
-
Part 2:元素访问与特殊方法
-
1、在Python中,协议通常指的是规范行为的规则,类似于接口。协议指定应该实现哪些方法以及这些方法应该做些什么。
-
2、基本的序列和映射协议:序列和映射基本上是元素的集合,要实现他们的基本行为(协议),不可变对象需要实现2个方法,而可变对象需要实现4个。要创建自定义的序列或映射,必须实现序列和协议指定的所有方法。
对于上述方法,还有一些额外的要求:
1、对于序列,如果键为负整数,应从末尾往前数。换而言之,x[-n]应与x[len(x)-n]等效。
2、如果键的类型不合适(如对序列使用字符串键),可能引发TypeError异常。
3、对于序列,如果索引的类型是正确的,但不在允许的范围内,应引发IndexError异常。
# 创建一个无穷的算术序列(没有__len__) def check_index(key): """ 指定的键是否可以索引? 1、键必须为非负整数 2、若非整数,引发TypeError;若为负数,引发IndexError """ if not isinstance(key, int): raise TypeError('The index must be an integer!') if key < 0: raise IndexError('The index must be a non-negative number!') class Arithmetic_Sequence: def __init__(self, start=0, step=1): """ 初始化算数序列,满足递推公式而且可以修改其中的值 start:序列的第一个值 step:序列相邻两元素的差值 changed:记录修改过的值 """ self.start = start self.step = step self.changed = {} def __getitem__(self, key): # 对实例的下标访问会自动转换为调用__getitem__方法 check_index(key) # 检查索引 try: return self.changed[key] # 查询是否存在对应的值,没有则引发KeyError except KeyError: return self.start + key * self.step # 根据递推公式计算值 def __setitem__(self, key, value): # 对实例设置值自动转换为__setitem__方法 check_index(key) self.changed[key] = value # 储存修改后的值 obj = Arithmetic_Sequence(1,2) # start为1,step为2 print(obj[10],obj[20]) # 21 41 # print(obj['lhz'],obj[-99]) # 报错正常 obj[10] = 999;print(obj[10]) # 设置值正常 del obj[10] # AttributeError: __delitem__ 没有对应的方法,无法删除
-
3、从
list
、dict
和str
派生:从哪一类派生,则该类的对象就为对应的类别,下举list特例# 实现一个带访问计数器的列表 class CounterList(list): def __init__(self,*args): super().__init__(*args) self.count = 0 def __getitem__(self,index): self.count += 1 return super().__getitem__(index) c1 = CounterList(range(10)) print(c1) # 继承list类则该类对象为list类型 print(c1,c1.reverse())# 此行可以看出同一行输出函数先调用再输出 del c1[0:3] print(c1.count,c1[0]+c1[1]+c1[1],c1.count) # 继承了list中的默认方法 # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] None # 0 16 3
Part 3:特性 property
-
1、函数property:可以将新的属性关联到方法和已知属性,通过存取方法来定义属性,可以在存取方法内对新属性进行一些限定,直接定义则无法对新属性限定
class Rectangle: def __init__(self): self.width = 0 self.height = 0 def set_size(self, size): self.width,self.height = size def get_size(self): return self.width, self.height size = property(get_size,set_size) >>> r = Rectangle() >>> r.width = 10 >>> r.height = 5 >>> r.size (10, 5) >>> r.size = 150, 100 # 设置时调用对应的方法,size属性依然受制于width和height >>> r.width 150 # 以下为官方文档3.8.6 """ Property attribute. fget:function to be used for getting an attribute value fset:function to be used for setting an attribute value fdel:function to be used for del'ing an attribute doc :docstring Typical use is to define a managed attribute x: class C(object): def getx(self): return self._x def setx(self, value): self._x = value def delx(self): del self._x x = property(getx, setx, delx, "I'm the 'x' property.") Decorators make defining new properties or modifying existing ones easy: """
-
2、静态方法和类方法:将它们分别包装在staticmethod和classmethod类的对象中,静态方法中没有参数self,可以直接通过类来调用。类方法的定义中包含类似于self的参数,通常被命名为cls。对于类方法,也可以通过对象直接调用,但参数cls将自动关联到类。
class MyClass: def smeth(): print('this is a static method') smeth = staticmethod(smeth) def cmeth(cls): print('this is a class method of',cls) cmeth = classmethod(cmeth) # 像上面两种包装和替换的方法比较繁琐,在python2.4中引入了装饰器 # 可以指定一个或多个装饰器,可以用@列出多个装饰器 # 指定多个装饰器时,应用的顺序和列出的顺序相反 class Myclass: @staticmethod def smeth(): print('this is a static method') @classmethod def cmeth(cls): print('this is a class method')
-
3、
__getattr__、__setattr__
等特殊方法 该类特殊方法会拦截对对象的所有访问企图,其用途之一是在旧式类中实现特性property(在旧式类中,property的行为可能不符合预期)。要在属性被访问时执行一段代码,必须使用一些特殊方法。
'''四类特殊方法(旧式类中,只需要使用后面三个) __getattribute__(self,name):在属性被访问时自动调用(只适用于新式类) __getattr__(self,name):在属性被访问而对象没有这样的属性时自动调用(属性查找失败时调用,兜底) __setattr__(self,name,value):试图给属性赋值时自动调用 __delattr__(self,name):试图删除属性时自动调用 ''' # 举例1:特殊方法的应用 class Rectangle: # 版本2,版本1见Part 3,1 def __init__ (self): self.width = 0 self.height = 0 def __setattr__(self, name, value): if name == 'size': self.width, self.height = value else: self. __dict__[name] = value def __getattr__(self, name): if name == 'size': return self.width, self.height else: raise AttributeError() ''' 这个版本的需要考虑更多的细节。 1、如果涉及的属性不是size也会调用方法__setattr__。因此这个方法必须考虑size和非size的情况:如果涉及到size则执行与以前相同的操作;若为新的属性就执行特殊属性__dict__,该属性是一个包含所有的实例属性的字典。不能执行常规的属性赋值,因为会反复调用__setattr__进入无限循环。 2、仅当没有找到指定属性时,才会调用方法__getattr__,即如果属性不是size则会引发AttributeError异常 ''' # 举例2:__getattr__方法,像访问属性一样访问字典 class Dict(dict): def __init__(self, *args, **kwargs):# init初始化对象自动转换为__setattr__调用 super(Dict, self).__init__(*args, **kwargs) def __getattr__(self, name): value = self[name] if isinstance(value, dict): value = Dict(value) return value obj = Dict({'lhz':1234,'attr':{'age':20,'sex':'man'}}) # lhz传入时被转换为str类型,若字典键为Number则obj.key不可用 print(obj, obj['lhz'] is obj.lhz, sep='\n') print(obj.attr) # {'lhz': 1234, 'attr': {'age': 20, 'sex': 'man'}} # True # {'age': 20, 'sex': 'man'}
**注意:**编写方法
__setattr__
时要注意避开无限循环,编写__getattribute__
时也应注意。该特殊方法拦截对所有属性的访问,因此将拦截对__dict__
的访问!唯一安全的方式是使用超类的__getattribute__
(使用super方法)
Part 4:迭代器和生成器
-
1、迭代器协议:for循环可以迭代实现
__iter__
方法的对象,该方法返回一个迭代器,是包含__next__
方法的对象,调用该方法可以不提供任何参数。当调用方法__next__
时,迭代器应返回其下一个值,如果无值可以返回,应该引发StopIteration异常。还可以使用内置的函数next,这种情况下next(it)
与it.__next__()
等效。# 斐波那契数列的迭代器方法: class Fibs: def __init__(self): self.a = 0 self.b = 1 def __next__(self): # 实现__next__才是迭代器 self.a,self.b = self.b,self.a+self.b return self.a def __iter__(self): # 无__iter__不可迭代 return self # 返回迭代器本身,也可以是其他迭代器,python根据返回迭代器的类型去找对应的__next__并执行 # 一般情况下,都在另一个对象中实现__iter__,并在for循环中使用这个对象 # 但是推荐在迭代器中也实现方法__iter__并返回self,这样迭代器就可以直接用于for循环中 # 例:for i in Fibs(): # for循环会先检查__iter__,然后进入__next__依次执行,每次返回一个值赋给i # 通过对可迭代对象调用内置函数iter,可以获得一个迭代器 >>> it = iter([1,2,3]) >>> next(it) 1 >>> next(it) 2
-
2、迭代器创建序列:将返回的可迭代对象直接转换为list类型
class TestIterator: value = 0 def __next__(self): self.value += 1 if self.value > 10:raise StopIteration # 遇到StopIteration停止迭代 return self.value def __iter__(self): return self print(list(TestIterator())) # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
-
3、生成器:包含yield语句的函数都被称为生成器,生成器被调用时不会执行函数体内的代码,而是返回一个迭代器。每次使用yield生成一个值(类似于return)之后,函数将冻结,等待被唤醒。被重新唤醒后,从停止的地方开始继续执行。
# 生成器由两个单独的部分组成:生成器函数和生成器的迭代器 # 1、生成器函数是由def定义的,其中包含yield # 2、生成器的迭代器是这个函数返回的结果,可以像其他迭代器一样使用 # 以上两个实体通常被视为一个,统称为生成器 def fun(): for i in range(2): print(i) yield i print('***') print(type(fun())) for i in fun(): print('===') ''' <class 'generator'> 0 === *** 1 === # 此时遍历没有结束,最后一次遍历输出最后一个'***' *** ''' # 生成器遍历列表 def flatten(nested): for sublist in nested: for element in sublist: yield element
生成器推导:工作原理与列表推导相同,但不是创建一个列表,而是返回一个生成器,使其能够逐步执行计算
g = ((i+2)**2 for i in range(2,10))
(使用next进行迭代)# 也可以这样 sum( i**2 for i in range(10))
-
4、递归式生成器:处理任意层嵌套的列表
def flatten(nested): try: for sublist in nested: # nested为一个元素则引发TypeError for element in flatten(sublist): yield element except TypeError: yield nested # 该方案存在问题是,无法处理字符串为元素的序列 # 依次检查字符串每个元素时会陷入无穷递归,字符串的第一个元素也是字符串 # 长度为1的字符串的第一个元素是它本身 # 完善后如下: def flatten(nested): try: try: nested+'' except TypeError:pass # 忽略由字符串非法相加引起的异常 else: raise TypeError for sublist in nested: for element in flatten(sublist): yield element except: yield nested
-
5、生成器方法:生成器开始运行后,可以使用生成器和外部之间的通信渠道来提供值。在挂起的生成器内部,yield可能用作表达式,即生成器重新运行时,yield返回一个通过send函数从外部发送的值。如果使用next,yield将返回None
def gen(info): while True: new = (yield info) print(new) g = gen('hello') print(g.send(None)) # 第一次启动时发送info参数必须为None print(g.send('world')) ''' hello # 第6行第一次yield输出info后挂起 world # 第7行从上次yield开始,输出信息'world' hello # 再次进入循环输出info ''' # 由上可知,仅当生成器被挂起(遇到第一个yield)后使用send才有意义 # send参数为None表示没有
方法throw:用在生成器中(yield处)引发异常,调用时可以提供一个异常类型、一个可选值和一个traceback对象
方法close:用于停止生成器,调用时无需提供任何参数(用时补充)、
-
补充:
函数 描述 iter(obj)
从可迭代对象创建一个迭代器 super(class,obj)
返回一个超类的关联实例