Python - 学习笔记

学习地址:Python 教程
其他:官网-函数列表

一、基础笔记

列表:list

len() 函数可以获得 list 元素的个数:

Python
1
>>> len(classmates)

list 是一个可变的有序表,所以,可以往 list 中追加元素到末尾:

Python
1
>>> classmates.append('Adam')

要删除 list 末尾的元素,用 pop() 方法。要删除指定位置的元素,用 pop(i) 方法,其中 i 是索引位置。

循环:for 和 while

for 循环:range() 函数,可以生成一个整数序列,再通过 list() 函数可以转换为list。

while 循环:continue 语句,跳过当前的这次循环,直接开始下一次循环。break 语句可以提前退出循环。

dict

通过 in 判断 key 是否存在:

Python
1
>>> 'Thomas' in d

通过 dict 提供的 get() 方法,如果 key 不存在,可以返回 None,或者自己指定的 value

Python
1
2
>>> d.get('Thomas')
>>> d.get('Thomas', -1)

要删除一个 key ,用 pop(key) 方法,对应的 value 也会从 dict 中删除:

Python
1
>>> d.pop('Bob')

set

setdict 类似,也是一组 key 的集合,但不存储 value。由于 key 不能重复,所以,在 set 中,没有重复的 key

通过 add(key) 方法可以添加元素到 set 中,可以重复添加,但不会有效果,通过 remove(key) 方法可以删除元素。

参数组合

Python 中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。

切片

取前 3 个元素,用一行代码就可以完成切片:

Python
1
>>> L[0:3]

Python 支持 L[-1] 取倒数第一个元素,那么它同样支持倒数切片,试试:

Python
1
2
3
4
>>> L[-2:]
['Bob', 'Jack']
>>> L[-2:-1]
['Bob']

所有数,每5个取一个:

Python
1
>>> L[::5]

列表生成式

列表生成式即 List Comprehensions,是 Python 内置的非常简单却强大的可以用来创建 list 的生成式。

举个例子,要生成 list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]可以用 list(range(1, 11))

Python
1
2
>>> list(range(1, 11))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

但如果要生成[1x1, 2x2, 3x3, …, 10x10]怎么做?方法一是循环:

Python
1
2
3
4
5
6
>>> L = []
>>> for x in range(1, 11):
... L.append(x * x)
...
>>> L
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

但是循环太繁琐,而列表生成式则可以用一行语句代替循环生成上面的 list

Python
1
2
>>> [x * x for x in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

写列表生成式时,把要生成的元素x * x放到前面,后面跟for循环,就可以把list创建出来,十分有用,多写几次,很快就可以熟悉这种语法。

for循环后面还可以加上if判断,这样我们就可以筛选出仅偶数的平方:

Python
1
2
>>> [x * x for x in range(1, 11) if x % 2 == 0]
[4, 16, 36, 64, 100]

还可以使用两层循环,可以生成全排列:

Python
1
2
>>> [m + n for m in 'ABC' for n in 'XYZ']
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']

二、函数式编程

map 函数:接收一个函数和一个序列,并把传入的函数作用于每个元素

reduce 函数:把一个函数作用在一个序列[x1, x2, x3, …]上,这个函数必须接收两个参数,reduce 把结果继续和序列的下一个元素做累积计算,其效果就是:

Python
1
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

fiter函数:和 map 类似,区别是会根据返回值是 TrueFalse 决定保留还是丢弃该元素

sorted函数:排序函数,可通过key实现自定义排序。可传入第三个参数 reverse=True,要进行反向排序,不必改动 key 函数。

Python
1
sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)

匿名函数:lambda x: x * x实际上就是:

Python
1
2
def f(x):
return x * x

关键字 lambda 表示匿名函数,冒号前面的 x 表示函数参数。

装饰器——?:需要再进一步了解。

偏函数:functools.partial 就是帮助我们创建一个偏函数的,不需要我们自己定义 int2(),可以直接使用下面的代码创建一个新的函数 int2

Python
1
2
3
>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
  1. 定义:对原函数的默认参数进行改造,为我所用
  2. 方式:max8=functools.partial(max,8)(当然要先导入 functools 包)
  3. 新知:int 居然这么强,int('1234234',2)

三、调试

程序能一次写完并正常运行的概率很小,基本不超过 1%。总会有各种各样的 bug 需要修正。有的 bug 很简单,看看错误信息就知道,有的 bug 很复杂,我们需要知道出错时,哪些变量的值是正确的,哪些变量的值是错误的,因此,需要一整套调试程序的手段来修复 bug
第一种方法简单直接粗暴有效,就是用 print() 把可能有问题的变量打印出来看看:

Python
1
2
3
4
5
6
7
8
9
def foo(s):
n = int(s)
print('>>> n = %d' % n)
return 10 / n

def main():
foo('0')

main()

执行后在输出中查找打印的变量值:

Python
1
2
3
4
5
$ python ~/documents/err.py
>>> n = 0
Traceback (most recent call last):
...
ZeroDivisionError: integer division or modulo by zero

print() 最大的坏处是将来还得删掉它,想想程序里到处都是 print(),运行结果也会包含很多垃圾信息。所以,我们又有第二种方法。

断言

凡是用 print() 来辅助查看的地方,都可以用断言(assert)来替代:

Python
1
2
3
4
5
6
7
def foo(s):
n = int(s)
assert n != 0, 'n is zero!'
return 10 / n

def main():
foo('0')

assert 的意思是,表达式 n != 0 应该是 True,否则,根据程序运行的逻辑,后面的代码肯定会出错。
如果断言失败,assert 语句本身就会抛出

Python
1
2
3
4
5
AssertionError:
$ python ~/documents/err.py
Traceback (most recent call last):
...
AssertionError: n is zero!

程序中如果到处充斥着 assert,和 print() 相比也好不到哪去。不过,启动 Python 解释器时可以用 -O 参数来关闭assert

Python
1
2
3
4
$ python -O err.py
Traceback (most recent call last):
...
ZeroDivisionError: division by zero

关闭后,你可以把所有的 assert 语句当成pass 来看。

logging

print() 替换为 logging 是第3种方式,和 assert 比,logging 不会抛出错误,而且可以输出到文件:

Python
1
2
3
4
5
6
import logging

s = '0'
n = int(s)
logging.info('n = %d' % n)
print(10 / n)

logging.info() 就可以输出一段文本。运行,发现除了 ZeroDivisionError,没有任何信息。怎么回事?
别急,在 import logging 之后添加一行配置再试试:

Python
1
2
import logging
logging.basicConfig(level=logging.INFO)

看到输出了:

Python
1
2
3
4
5
6
$ python err.py
INFO:root:n = 0
Traceback (most recent call last):
File "err.py", line 8, in <module>
print(10 / n)
ZeroDivisionError: division by zero

这就是 logging 的好处,它允许你指定记录信息的级别,有 debuginfowarningerror 等几个级别,当我们指定 level=INFO 时,logging.debug 就不起作用了。同理,指定 level=WARNING 后,debuginfo 就不起作用了。这样一来,你可以放心地输出不同级别的信息,也不用删除,最后统一控制输出哪个级别的信息。
logging 的另一个好处是通过简单的配置,一条语句可以同时输出到不同的地方,比如console 和文件。

pdb

第4种方式是启动 Python 的调试器 pdb,让程序以单步方式运行,可以随时查看运行状态。我们先准备好程序:

Python err.py
1
2
3
s = '0'
n = int(s)
print(10 / n)

然后启动:

终端
1
2
3
$ python -m pdb err.py
> /Users/michael/Github/learn-python3/samples/debug/err.py(2)<module>()
-> s = '0'

以参数 -m pdb 启动后,pdb 定位到下一步要执行的代码-> s = '0'。输入命令 l 来查看代码:

Python
1
2
3
4
5
(Pdb) l
1 # err.py
2 -> s = '0'
3 n = int(s)
4 print(10 / n)

输入命令n可以单步执行代码:

Python
1
2
3
4
5
6
(Pdb) n
> /Users/michael/Github/learn-python3/samples/debug/err.py(3)<module>()
-> n = int(s)
(Pdb) n
> /Users/michael/Github/learn-python3/samples/debug/err.py(4)<module>()
-> print(10 / n)

任何时候都可以输入命令p 变量名来查看变量:

Python
1
2
3
4
(Pdb) p s
'0'
(Pdb) p n
0

输入命令q结束调试,退出程序:

Python
1
(Pdb) q

这种通过 pdb 在命令行调试的方法理论上是万能的,但实在是太麻烦了,如果有一千行代码,要运行到第 999 行得敲多少命令啊。还好,我们还有另一种调试方法。

Python
1
pdb.set_trace()

这个方法也是用 pdb,但是不需要单步执行,我们只需要 import pdb,然后,在可能出错的地方放一个 pdb.set_trace(),就可以设置一个断点:

Python err.py
1
2
3
4
5
6
import pdb

s = '0'
n = int(s)
pdb.set_trace() # 运行到这里会自动暂停
print(10 / n)

运行代码,程序会自动在 pdb.set_trace() 暂停并进入 pdb 调试环境,可以用命令 p 查看变量,或者用命令 c 继续运行:

终端
1
2
3
4
5
6
7
8
9
10
$ python err.py 
> /Users/michael/Github/learn-python3/samples/debug/err.py(7)<module>()
-> print(10 / n)
(Pdb) p n
0
(Pdb) c
Traceback (most recent call last):
File "err.py", line 7, in <module>
print(10 / n)
ZeroDivisionError: division by zero

这个方式比直接启动 pdb 单步调试效率要高很多,但也高不到哪去。

IDE

如果要比较爽地设置断点、单步执行,就需要一个支持调试功能的 IDE。目前比较好的 Python IDE 有:
Visual Studio Code:需要安装Python 插件。
PyCharm
另外,Eclipse 加上 pydev 插件也可以调试 Python 程序。

小结:写程序最痛苦的事情莫过于调试,程序往往会以你意想不到的流程来运行,你期待执行的语句其实根本没有执行,这时候,就需要调试了。
虽然用 IDE 调试起来比较方便,但是最后你会发现,logging 才是终极武器。