Python学习笔记(二)

第三章 选择与循环

1. 条件表达式

异或:符号 ^ ,就是说两个值不相同,则异或结果为真。反之,为假。 不同为1,相同为0。

Python中值为0、False、None或空对象均被视为False

1
2
3
4
>>> print(1<2<3)   # 运算符可以连续使用
True
>>> print(1<3>2)
True

and和or支持惰性求值,前面的算完了后面的可能不算,可以设计表达式的前后关系提高效率

条件表达式中不能使用赋值=

2. 选择结构

Python支持如下语句用来代替Java的 ? : 运算符

1
2
3
4
5
6
value_1 if condition_sentence else value_2   # value可以是复杂语句

>>> import math # 没导入random模块
>>> x = math.sqrt(4) if 3>2 else random.randint(0,3) # 由于惰性求值,不会报错
>>> x
2.0

多分支选择结构

1
2
3
4
5
6
if cond1:
value1
elif cond2:
value2
else:
value3

选择结构嵌套时注意缩进

日期模块

1
2
3
4
5
6
7
8
>>> import datetime
>>> x = datetime.date.today() # 查看今天
>>> x
datetime.date(2022, 1, 17)

>>> now = datetime.datetime.now()
>>> now
datetime.datetime(2022, 1, 17, 19, 13, 41, 774272)

3. 循环结构

while和for语句均可带上一个else子句,表示在条件不满足时执行一次else中的语句,但如果训话不是自然结束而是由于break,则else中的语句不会执行。

break和continue也可以继续使用

1
2
3
4
5
6
7
8
>>> a = [1,'我',('#',6),'*',0]
>>> for i,v in enumerate(a): # 遍历列表
... print('第',i+1,'个成员是:',v) # print('第{0}个成员是:{1}'.format(i+1,v))
1 个成员是: 1
2 个成员是: 我
3 个成员是: ('#', 6)
4 个成员是: *
5 个成员是: 0

Python标准库itertools:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
>>> import itertools
>>> x = 'An apple a day.'
>>> y = itertools.cycle(x) # 循环元素
>>> for i in range(20):
... print(next(y),end=',')
A,n, ,a,p,p,l,e, ,a, ,d,a,y,.,A,n, ,a,p,

# 序列分组
def group(num):
if num > 10:
return '大于10'
elif num < 5:
return '小于5'
else:
return '在5和10之间'


if __name__ == '__main__':
x = range(20)
y = itertools.groupby(x, group)
for k, v in y: # y也是键值对形式
print(k, ':', list(v))

>>> x = itertools.permutations([1,2,3,4],3) # [1,2,3,4]的全排列
>>> next(x)
(1, 2, 3, 4)
>>> next(x)
(1, 2, 4, 3)

第四章 字符串与正则表达式

用以下两种方式指定Python程序的编码:

1
2
#coding=utf-8
#- * -coding:utf-8 - * -

1. 字符串

Python的字符串驻留机制似乎一直在改变之中,在《Python程序设计》(第二版)的书中描述的驻留特点在我的Python 3.8.2版本中已经过时,经过本机试验和网络资料,现在简单描述一下此版本的机制:

  • 字符串长度为0和1时,默认都采用了驻留机制。
  • 字符串长度超过4096,默认都不驻留。
  • 字符串长度大于1时,
    • 只包含大小写字母、数字、下划线时,默认驻留。
    • 包含其余符号则不驻留。
  • 用乘法得到的字符串中,
    • 乘数为1时,
      • 仅包含大小写字母、数字、下划线时,默认驻留。
      • 含其他字符串时,仅在总长<=1是驻留。
    • 乘数大于1时,与非乘法的字符串规则一致。

1.1 格式化

推荐使用format() 方法进行格式化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
>>>"{} {}".format("hello", "world")    # 不设置指定位置,按默认顺序
'hello world'

>>> "{0} {1}".format("hello", "world") # 设置指定位置
'hello world'

>>> "{1} {0} {1}".format("hello", "world") # 设置指定位置
'world hello world'

>>> print("the number {0:,} in hex is: {0:#x}; the number {1:,} in oct is {1:#o}".format(123,555)) # 另外#b是二进制
the number 123 in hex is: 0x7b; the number 555 in oct is 0o1053

>>> print('My name is {name}, age is {age}'.format(name = 'Harry',age = 10))
My name is Harry, age is 10

>>> nums = [23,41,20]
>>> print('A = {0[0]},B = {0[1]},C = {0[2]}'.format(nums)) #取序列的值
A = 23,B = 41,C = 20

1.2 常用方法

函 数 说 明
str1.find(str2[,start[,end]]) 在str1中找到str2,返回开始下标(找不到返回-1),end取不到
str1.rfind(str2) 从str1尾部向前找
str1.index(str2) 返回str2首次出现的位置,不存在则抛异常
str1.rindex(str2) 从str1尾部向前找
str1.count(str2) str2在str1中出现的次数
split() 以指定字符从字符串左端开始分割,返回字符串列表;若不指定分隔符,则空格、换行、制表符都被认为是分隔符
rsplit() 以指定字符从字符串右端开始分割,返回字符串列表
partition(); rpartition(); 以指定字符从字符串某一侧开始将其分割分成三部分:分隔符前的内容,分隔符,分隔符后的内容
str.join(list) 用str作为分隔符连接list内的各成员
lower(); upper() 字符串每个字母全转为小/大写
capitalize() 第一个位置上若是字母,则变为大写,其余小写
title() 单词第一字母大写
swapcase() 大小写互换
str.replace(a,b) 替换str中所有的a为b
maketrans(); translate() 自定义字符串替换
strip(), rstrip(), lstrip() 删除两端,左端,右端的空白或连续的指定字符
eval() 尝试将str转成有意义的表达式并求之,注意异常
关键字 in 判断是否在字符串中,返回布尔值
startswith(), endswith() 字符串是否以其开头,还可限定起止范围,返回布尔值
isalnum() 是否是数字或者字母
isalpha() 是否是字母
isdigit() 是否是数字字符
isspace() 是否是空白字符(包括\n \t 等)
isupper() 是否全为大写字母
islower() 是否全为小写字母
center(); ljust(); rjust() 返回指定宽度的新字符串,居中、靠左对齐、靠右对齐
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
>>> 'aabbcc'.count('c')   # 计算c的个数
2
>>> 'aabbcc'.count('') # 传空字符串
7 # 计算“缝隙”的个数

>>> x = 'I have a dream.'
>>> x.partition(' ')
('I', ' ', 'have a dream.')

# maketrans(); translate()
>>> x = 'Hello world, I am python.'
>>> s = x.maketrans('ao','xy') # 这里的替换大小写敏感
>>> x.translate(s)
'Helly wyrld, I xm pythyn.' # 可以发现a对应x、o对应y

>>> x = 'asdfdaf'
>>> x.strip('af')
'sdfd' # 注意是把a和f全去掉了

>>> x = 'python'
>>> x.center(10)
' python ' # 居中
>>> x.ljust(10)
'python ' # 靠左
>>> x.rjust(10)
' python' # 靠右

字符串频率统计—— collections.Counter举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
>>> from collections import Counter
>>> ct = Counter('aabcbacbcabcbacb')
>>> ct
Counter({'b': 6, 'a': 5, 'c': 5}) # 注意也可以用dict()转成字典
>>> ct['a'] # 取某元素的频次
5
>>> ct.most_common(1) # 统计出现次数最多的元素
[('b', 6)]
>>> ct.most_common(2) # 统计出现次数最多的两个元素
[('b', 6), ('a', 5)]
>>> ct.most_common() # most_common()无参数则列出全部
[('b', 6), ('a', 5), ('c', 5)]
>>> list(ct.elements()) # 按照元素出现次数生成列表
['a', 'a', 'a', 'a', 'a', 'b', 'b', 'b', 'b', 'b', 'b', 'c', 'c', 'c', 'c', 'c']
>>> sorted(ct) # 排序,按序输出列表
['a', 'b', 'c'] # 注意重复元素只出现一次

# Counter对象之间可以相减(有什么用?见LeetCode 748. 最短补全词)
>>> x = [1,1,2,3,4,4,5,6,7,5]
>>> y = [1,2,2,6]
>>> cx,cy = Counter(x),Counter(y)
>>> cx
Counter({1: 2, 4: 2, 5: 2, 2: 1, 3: 1, 6: 1, 7: 1})
>>> cy
Counter({2: 2, 1: 1, 6: 1})
>>> cx-cy
Counter({4: 2, 5: 2, 1: 1, 3: 1, 7: 1}) # 各个词频相减
# 注意如果词频相减有负数,不放入最终结果
>>> m,n = 'abbc','aabcd'
>>> cm,cn = Counter(m),Counter(n)
>>> cm,cn
(Counter({'b': 2, 'a': 1, 'c': 1}), Counter({'a': 2, 'b': 1, 'c': 1, 'd': 1}))
>>> cm-cn
Counter({'b': 1}) # 只有'b'的频数相减为正,故结果里只有'b'

1.3 字符串常量

1
2
3
4
5
6
7
8
9
10
11
12
13
import string
string.digits
string.punctuation # 标点符号
string.ascii_letters # 大小写字母
string.printable # 可打印字符
string.ascii_lowercase
sting.ascii_uppercase

# 生成建议密码
def generate_password(size):
str_pool = string.digits + string.ascii_letters + string.punctuation
pwd1 = ''.join(random.choice(str_pool) for _ in range(size))
pwd2 = ''.join(random.sample(str_pool, size))

其中,random模块常用函数

函 数 说 明
choice(seq) 从序列中任选一个
sample(seq,size) 从序列中不重复地选择size个
getrandbits(x) 生成二进制最大是x位的随机整数(如x=3时会返回000之类的)
randrange(); randint() 返回随机整数,前者左闭右开,后者左闭右闭
shuffle(seq) 原地打乱

1.4 可变字符串

要用 io.StringIOarray 模块

2. 正则表达式

2.1 语法

元字符

表 达 式 含 义
expr* 表达式expr出现0次或更多次
expr+ 表达式expr出现1次或多次
expr? 表达式expr出现0或1次
expr{n} 表达式expr正好出现n次
expr{m,n} 表达式expr最少出现m次,最多n次(expr{0,1}⟺expr?)
expr{m,} 表达式expr最少出现m次
expr{,n} 表达式expr最多出现n次
\d 任意一个数字(0~9)
\D 任意一个非数字
\s 任意一个空白字符(如换页符、制表符)
\S 任意一个非空白字符
\w 任意一个数字、字母或下划线
\W 与\w相反
\b 匹配单词头或尾
\B 匹配单词中间
() 将括号内容作为一个整体来对待
[] 匹配位于括号内的任意一个字符
- 用于[]内,表示范围
. 匹配除换行符外的任意单个字符
^ 表示行的开头,匹配以^之后字符开头的字符串
$ 表示行的结束,匹配以$之前字符结尾的字符串
[^expr] 匹配表达式expr所表示的以外的字符
expr_1|expr_2 匹配表达式expr_1或expr_2

[\u4e00-\u9fa5]:匹配是否为汉字

2.2 re 模块

主要方法

方 法 说 明
compile(pattern[, flags]) 创建模式对象
search(pattern, string[, flags]) 在整个字符串中寻找模式,返回match对象或None
match(pattern, string[, flags]) 从字符串的开始处匹配模式,返回match对象或None
findall(pattern, string[, flags]) 列出字符串中模式的所有匹配项,返回一个列表
split(pattern, string[, maxsplit=0]) 根据模式匹配分割字符串
sub(pat, repl[, count=0]) 将字符串中所有pat匹配项用repl替换
escape(string) 将字符串中所有特殊正则表达式字符转义

其中 mathc()search()都返回一个match对象,该对象常用的方法有group()、groups()、groupdict()、start()、end()、span()等。

第五章 函数设计与使用

1. 形参与实参

在函数内部修改形参不会改变实参的值

1
2
3
4
5
6
7
8
9
10
def add(a):
print(a)
a += 1
print(a)


if __name__ == '__main__':
a = 2
add(a)
print(a)

输出:

1
2
3
2
3
2 # a的值并未被改变

2. 参数类型

2.1 默认值参数

定义函数时为形参设置默认值,语法:

1
2
3
def func_name(..., 形参名=默认值):
函数体
func_name.__defaults__ # 查看函数所有默认值

定义带有默认值参数的函数时,默认值参数必须出现在函数形参列表的最右端,即默认值参数的右边不能再出现非默认值参数。

注意 默认值参数只在第一次调用时进行解释,可能会有意外的错误,例如:

1
2
3
4
5
6
7
8
9
>>> def dd(new, old_list=[]):
... old_list.append(new)
... return old_list
>>> print(dd('5',[1,2,3]))
[1, 2, 3, '5'] # 正常调用
>>> print(dd('a')) #第一次调用
['a']
>>> print(dd('b')) #第二次调用
['a', 'b'] # old_list 默认继承了上一次的结果

2.2 关键参数

可以按照参数名字传值,而不用牢记形参定义时的顺序,例如:

1
2
3
4
>>> def pp(a, b, c):
... print(a, b, c)
>>> pp(c = 3, a = 1, b = 2)
1 2 3

2.3 可变长度参数

有两种形式:

  1. *parameter,接收任意多个参数并放入元组中,如:
1
2
3
4
5
>>> def demo(*p):
... print(p)
...
>>> demo('a',1,'haha')
('a', 1, 'haha')
  1. **parameter,类似关键参数,调用函数时显示赋值给形参:
1
2
3
4
5
6
7
def demo(**p):
for item in p.items():
print(item) # item是元组类
>> demo(x=1, y=2, z='string')
('x', 1)
('y', 2)
('z', 'string')

2.4 参数传递时序列解包

可以使用列表、元组、集合、字典等序列为含多个变量的函数传递参数,在实参前加一个*进行解包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> def plus(a, b, c):
... print(a+b+c)
...
>>> seq = [1,2,3]
>>> plus(*seq)
6
>>> tup = (3,4,5)
>>> plus(*tup)
12
>>> dict= {1:'a',2:'b',3:'c'}
>>> plus(*dict) # 默认操作key
6
>>> dict= {'a':1,'b':2,'c':3}
>>> plus(*dict.values())
6

务必保证序列元素个数与参数个数一致。

3. 变量作用域

变量已在函数外定义,在函数内引用可直接用,而要修改该变量的值需要在函数内用global声明这个变量为全局变量,明确使用同名全局变量。

在函数内部直接使用global关键字将一个变量声明为全局变量,即使在函数外没有定义该全局变量,在调用这个函数后将自动增加新的全局变量。

如果局部变量与全局变量名字相同,局部变量会将全局变量隐藏,例如:

1
2
3
4
5
6
>>> def dd():
... x = 2
... print(x)
>>> x = 10
>>> dd()
2

4. lambda 表达式

用于声明没有函数名字而临时定义的匿名函数,只可以包含一个表达式,不允许包含复杂语句,但可以调用其他的函数,支持默认值参数和关键参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> f = lambda x, y, z: x+ y+ z
>>> print(f(1,2,3))
6
>>> g = lambda x, y=2, z=3: x+y+z # 默认值参数
>>> print(g(1))
6
>>> print(g(2, z=4, y=5)) # 调用时使用关键参数
11
>>> L = [(lambda x: x**2), (lambda x: x**3), (lambda x: x**4)]
>>> print(L[0](3),L[1](2),L[2](2)) # 列表形式
9 8 16

>>> x = [1,2,3,4,5]
>>> res = map((lambda x:x*2),x) # 匿名函数,操作列表
>>> list(res)
[2, 4, 6, 8, 10]

使用lambda表达式时,要注意变量作用域的问题。外部定义的变量不是局部变量,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> r = []
>>> for x in range(10):
... r.append(lambda: x**2) # 错误写法,x不是局部变量
>>> r[0]()
81
>>> r[3]()
81
>>> r = []
>>> for x in range(10):
... r.append(lambda n=x: n**2) # 正确写法
...
>>> r[0]()
0
>>> r[1]()
1
>>> r[2]()
4

5. 高级话题

  • reduce()

接收两个参数,以累积的方式从左到右依次作用到一个序列或迭代器对象的所有元素上,例如:

1
2
3
4
>>> from functools import reduce
>>> seq = [1,2,3,4,5,6,7]
>>> reduce(lambda x,y:x+y,seq)
28
  • filter()

将一个单参数函数fun()作用到另一个序列上,返回该序列中使得fun()的返回值为True的那些元素组成的序列,例如:

1
2
3
4
5
6
7
>>> seq = ['12-1','aasd','**','3t']
>>> def func(x):
... return x.isalnum()
>>> filter(func,seq)
<filter object at 0x000001ED89839370> # 注意返回filter对象
>>> list(filter(func,seq))
['aasd', '3t']
  • yield()

书中讲得一知半解,参考这篇博文:《python中yield的用法详解——最简单,最清晰的解释》

总结一下:yield首先是起到return的作用,即返回值;带yield的函数是一个生成器,而不是一个函数,这个生成器有一个__next__() 函数,相当于“下一步”生成哪个数,这一次的__next__()开始的地方是接着上一次的__next__()停止的地方执行的。再看下面的示例:

1
2
3
4
5
6
7
8
9
10
>>> def f():
... a,b=1,1
... while True:
... yield a # 先返回a的值
... a,b=b,a+b
...
>>> a = f()
>>> for i in range(10):
... print(a.__next__(),end=' ')
1 1 2 3 5 8 13 21 34 55
  • 使用dis模块查看函数的字节码指令

  • 嵌套定义与可调用对象

1
2
3
4
5
6
7
8
9
10
11
12
13
def linear(a,b);
def result(x): # 函数定义嵌套
return a *x +b
return result

class linear:
def __init__(self, a, b): # 新建对象时候调用
self.a, self.b = a, b
def __call__(self, x): # 调用已有对象时用。任何包含__call__()的类的对象都是可调用的
return self.a *x +self.b

t = linear(0.3, 2) # 定义一个可调用对象
t(5) # 使用