VectorLu

廖雪峰 Python3 教程笔记

很多人推荐的 Python 教程

递归函数

汉诺塔的 Python 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
# coding: utf-8
def hanoi(n, A, B, C):
if 1==n:
print(A, '-->', C)
else:
hanoi(n-1, A, C, B)
hanoi(1, A, B, C)
hanoi(n-1, B, A, C)
# 注意input()的返回值是字符串,要强制转换为整型
n = int(input('Please input the layer of the hanoi tower:'))
hanoi(n, 'A', 'B', 'C')

迭代

判断一个对象是否可迭代

1
2
3
4
5
6
7
>>> from collections import Iterable
>>> isinstance('abc', Iterable) # str 是否可迭代
True
>>> isinstance('[1, 2, 3]', Iterable) # list 是否可迭代
True
>>> isinstance(123, Iterable) # 整数是否可迭代
False

对可迭代对象进行下标循环

1
2
3
4
5
6
>>> for i, value in enumerate(['A', 'B', 'C']):
... print(i, value)
...
0 A
1 B
2 C

同时引用多个变量

这在 Python 中非常常见且简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>>> for x, y in [(1, 1), (2, 4), (3, 9)]:
... print(x, y)
...
1 1
2 4
3 9
>>> for x, y, z in [(1, 1, 1), (2, 4, 8), (3, 9, 27)]:
... print(x, y, z)
...
1 1 1
2 4 8
3 9 27
>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> for k, v in d.items():
... print(k, '=', v)
...
x = A
y = B
z = C

List comprehensions 列表生成器

1
2
>>> import os
>>> [d for d in os.listdir('.')] # os.listdir() 可以列出文件和目录

generator 生成器

generator 保存的是算法,一边计算,一边循环。
定义生成器有多种方法:

方法一:将列表解析式的 [] 改为 ()

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
>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x10ef154c0>
>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g)
9
>>> next(g)
16
>>> next(g)
25
>>> next(g)
36
>>> next(g)
49
>>> next(g)
64
>>> next(g)
81
>>> next(g)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

generator 也是可迭代对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> g = (x * x for x in range(10))
>>> for n in g:
... print(n)
...
0
1
4
9
16
25
36
49
64
81

方法二: yield

函数是顺序执行,遇到 return 语句或执行完所有语句返回。但是使用 yield,执行到 yield 就停止执行,下一次从 yield 之后第一条语句开始执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!usr/bin/env python3
# -*- coding: utf-8 -*-
def fiboGenerator(num):
n, a, b = 0, 0, 1
while n < num:
yield b
a, b = b, a+b
n += 1
return 'done'
f = fiboGenerator(6)
for i in f:
print(i)

执行结果:

1
2
3
4
5
6
7
Root:others $ python3 fibo_generator.py
1
1
2
3
5
8

函数式编程

特点之一是:允许把函数本身作为参数传入另一个函数,还允许返回一个函数。由于 Python 允许使用变量,所以不是纯函数式编程语言。

高阶函数

把函数作为参数传入,这样的函数称为高阶函数

map() 函数

内置的 map(),第一个参数是任意函数,第二个参数是 Iterable,而返回 Iterator。(注意第二个参数和返回值的区别)

1
2
3
4
5
6
7
8
>>> def f(x):
... return x * x
...
>>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> list(r)
[1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
['1', '2', '3', '4', '5', '6', '7', '8', '9']

匿名函数

1
2
>>> list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
[1, 4, 9, 16, 25, 36, 49, 64, 81]

冒号前为参数,冒号后表达式的值就是要 return 的值。

装饰器

参考自:
Python修饰器的函数式编程
函数式编程

1
2
3
@decorator
def func():
pass

实际上解释器会将其解释为:

1
func = decorator(func)

偏函数

functools.partial 的作用是:把函数的某些参数固定,返回一个新函数,使任务更简单。

1
2
def int2(x, base=2):
return int(x, base)

即等价于:

1
2
import functools
int2 = functools.partial(int, base=2)

使用模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python3
# coding: utf-8
'a test module'
__author__ = 'VectorLu'
import sys
def test():
args = sys.argv
if len(args)==1:
print('Hello, world!')
elif len(args)==2:
print('Hello, %s!' % args[1])
else:
print('Too many arguments!')
if __name__ == '__main__':
test()

这就是 Python 模块的标准文件模版。

其中:

1
2
if __name__ == '__main__':
test()

当在命令行中运行该文件时,Python 解释器把 __name__ 置为 __main__ 如果该文件被当成模块导入,则该条件表达式为 False

不应该被外部引用的函数名或变量名前往往有 _

面向对象编程

不希望外部访问的对象(变量或函数)前加前缀 __

获取对象信息

type(object) 来判断类型。

普通变量

1
2
3
4
5
6
>>> type(123) == type(456)
True
>>> type(123)==int
True
>>> type('abc') == str
True

函数等

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> import types
>>def fn():
...     pass
...
>>> type(fn) == types.FunctionType
True
>>> type(abs) == types.BuiltinFunctionType
True
>>> type(lambda x: x) == types.LambdaType
True
>>> type((x for x in range(10))) == types.GeneratorType
True

另外:

1
2
3
4
5
6
>>> isinstance('a', str)
True
>>> isinstance(123, int)
True
>>> isinstance(b'a', bytes)
True

并且还可以判断变量是否是某些类型的一种:

1
2
3
4
>>> isinstance([123], (list, tuple))
True
>>> isinstance((123), (list, tuple))
True

使用 dir()

dir() 获取一个对象的所有属性和方法。

1
2
3
4
5
6
7
8
9
10
>>> dir('abc')
['__add__''__class__''__contains__''__delattr__''__dir__''__doc__''__eq__''__format__''__ge
__', '__getattribute__''__getitem__''__getnewargs__''__gt__''__hash__''__init__''__init_subclas
s__', '__iter__''__le__''__len__''__lt__''__mod__''__mul__''__ne__''__new__''__reduce__''
__reduce_ex__', '__repr__''__rmod__''__rmul__''__setattr__''__sizeof__''__str__''__subclasshook
__', 'capitalize''casefold''center''count''encode''endswith''expandtabs''find''format''fo
rmat_map', 'index''isalnum''isalpha''isdecimal''isdigit''isidentifier''islower''isnumeric''
isprintable', 'isspace''istitle''isupper''join''ljust''lower''lstrip''maketrans''partition'
'replace''rfind''rindex''rjust''rpartition''rsplit''rstrip''split''splitlines''startswi
th', 'strip''swapcase''title''translate''upper''zfill']

形似于 __xxx__ 这样的变量都是有特殊用途的。比如 __len__ 方法返回长度。以下代码是等价的。

1
2
3
4
>>> len('abc')
3
>>> 'abc'.__len__()
3

自己写的类,如果也想用 len() 方法,就要自己写一个 __len__()

1
2
3
4
5
6
7
>>class MyDog(object):
...     def __len__(self):
...         return 100
...
>>> dog = MyDog()
>>> len(dog)
100

限制实例属性

Python 是动态语言,创建类的实例之后可以给实例绑定属性和方法,只对该实例有效,对该类的其他实例无效。

先定义一个类:

1
2
>>class Student(object):
...     pass

然后给实例绑定一个属性:

1
2
3
4
>>> s = Student()
>>> s.name = 'Vector'
>>> print(s.name)
Vector

还可以给实例绑定一个方法:

1
2
3
4
5
>>> from types import MethodType
>>> s.set_age = MethodType(set_age, s)
>>> s.set_age(25)
>>> s.age
25

这个方法对该类的其他实例无效:

1
2
3
4
5
>>> s2 = Student()
>>> s2.set_age(25)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'set_age'

但是可以给类绑定方法,就用于该类的所有实例。

1
2
3
4
>>def set_score(self, score):
...     self.score = score
...
>>> Student.set_score = set_score

如果想要限制实例的属性:Python允许在定义class的时候,定义一个特殊的slots变量,来限制该class实例能添加的属性:

1
2
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称

需要注意的是 __slots__ 限制只对当前类起作用,对其子类是不起作用的,而且只能限制添加属性,不能限制通过添加方法的方式添加属性。

编写可迭代的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Fib(object):
def __init__(self):
self.a, self.b = 0, 1
def __iter__(self):
return self # 本身就是迭代对象,返回自己
def __next__(self):
self.a, self.b = self.b, self.a+self.b
if self.a > 10000:
raise StopIteration();
return self.a
for n in Fib():
print(n)

错误、调试和测试

错误处理

Python 内置了一套 try...except...finally... 的错误处理机制。

try

1
2
3
4
5
6
7
8
9
try:
print('try...')
r = 10/0
print('result:', r)
except ZeroDivisionError as e:
print('except:', e)
finally:
print('finally...')
print('END')

如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码,即 except 语句块,执行完 except 后,如果有 finally 语句块,则执行finally 语句块,至此,执行完毕。

如果没有发生,则 except 语句块不被执行。

但是 finally 如果有,则一定会被执行。

如果发生了不同类型的错误,应该由不同的 except 语句块处理。可以有多个 except 来捕获不同类型的错误。此外,如果没有错误发生,可以在 except 语句块后面加一个 else,当没有错误发生时,会自动执行 else 语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
try:
print('try...')
r = 10 / int('2')
print('result:', r)
except ValueError as e:
print('ValueError:', e)
except ZeroDivisionError as e:
print('ZeroDivisionError:', e)
else:
print('no error!')
finally:
print('finally...')
print('END')

Python的错误其实也是class,所有的错误类型都继承自BaseException,所以在使用except时需要注意的是,它不但捕获该类型的错误,还把其子类也“一网打尽”。

1
2
3
4
5
6
try:
foo()
except ValueError as e:
print('ValueError')
except UnicodeError as e:
print('UnicodeError')

第二个 except 永远也捕获不到 UnicodeError,因为 UnicodeErrorValueError 的子类,如果有,也被第一个 except 给捕获了。

记录错误

使用 logging 模块可以很容易地记录错误信息,而且在打印出错误信息之后可以继续执行。

没有使用 logging

1
2
3
4
5
6
7
8
9
10
11
# err.py:
def foo(s):
return 10 / int(s)
def bar(s):
return foo(s) * 2
def main():
bar('0')
main()

结果如下:

1
2
3
4
5
6
7
8
9
10
11
$ python3 err.py
Traceback (most recent call last):
File "err.py", line 11, in <module>
main()
File "err.py", line 9, in main
bar('0')
File "err.py", line 6, in bar
return foo(s) * 2
File "err.py", line 3, in foo
return 10 / int(s)
ZeroDivisionError: division by zero

从上往下可以看到整个错误的调用函数链。

如果不捕获错误,自然可以让Python解释器来打印出错误堆栈,但程序也被结束了。既然我们能捕获错误,就可以把错误堆栈打印出来,然后分析错误原因,同时,让程序继续执行下去。

使用 logging

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# err_logging.py
import logging
def foo(s):
return 10 / int(s)
def bar(s):
return foo(s) * 2
def main():
try:
bar('0')
except Exception as e:
logging.exception(e)
main()
print('END')

结果如下,同样会出错,但打印完错误信息会继续执行并且正常退出。

1
2
3
4
5
6
7
8
9
10
11
Root:liaoxuefeng $ python3 err_logging.py
ERROR:root:division by zero
Traceback (most recent call last):
File "err_logging.py", line 13, in main
bar('0')
File "err_logging.py", line 9, in bar
return foo(s) * 2
File "err_logging.py", line 6, in foo
return 10 / int(s)
ZeroDivisionError: division by zero
END

抛出错误

1
2
3
4
5
6
7
8
9
10
11
# err_raise.py
class FooError(ValueError):
pass
def foo(s):
n = int(s)
if n == 0:
raise FooError('invalid value: %s' % s)
return 10 / n
foo('0')

执行,最后跟踪到自定义的错误:

1
2
3
4
5
6
7
Root:liaoxuefeng $ python3 err_raise.py
Traceback (most recent call last):
File "err_raise.py", line 10, in <module>
foo('0')
File "err_raise.py", line 7, in foo
raise FooError('invalid value: %s' % s)
__main__.FooError: invalid value: 0

最后,我们来看另一种错误处理的方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# err_reraise.py
def foo(s):
n = int(s)
if n == 0:
raise ValueError('invalid value: %s' % s)
return 10 / n
def bar():
try:
foo('0')
except ValueError as e:
print('ValueError!')
raise
bar()

捕获错误目的只是记录一下,便于后续追踪。但是,由于当前函数不知道应该怎么处理该错误,所以,最恰当的方式是继续往上抛,让顶层调用者去处理。raise 语句如果不带参数,就会把当前错误原样抛出。此外,在 exceptraise 一个 Error,还可以把一种类型的错误转化成另一种类型:

调试

断言

1
2
3
4
5
6
7
8
9
10
# err.py
def foo(s):
n = int(s)
assert n != 0, 'n is zero!'
return 10 / n
def main():
foo('0')
main()

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

1
2
3
4
5
6
7
8
9
Root:liaoxuefeng $ python3 err.py
Traceback (most recent call last):
File "err.py", line 10, in <module>
main()
File "err.py", line 8, in main
foo('0')
File "err.py", line 4, in foo
assert n != 0, 'n is zero!'
AssertionError: n is zero!

启动 Python 解释器的时候可以用参数 -O 来关闭 assert,关闭后,可以把 assert 语句当成 pass 看。

logging

logging 允许你指定记录信息的级别,有 debuginfowarningerror 等几个级别,当我们指定 level=INFO 时,logging.debug 就不起作用了。同理,指定 level=WARNING 后,debuginfo 就不起作用了。这样一来,你可以放心地输出不同级别的信息,也不用删除,最后统一控制输出哪个级别的信息。

pdb

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

1
2
3
4
# pdbLearn.py
s = '0'
n = int(s)
print(10/n)

然后启动:

1
2
3
Root:liaoxuefeng $ python3 -m pdb pdbLearn.py
> /Users/rooooooot/git/PythonLearning/Python3/liaoxuefeng/pdbLearn.py(2)<module>()
-> s = '0'

以参数 -m pdb 启动后,pdb 定位到下一步要执行的代码 -> s = '0' 。输入命令 l (注意这里是小写字母 L,不是数字 1)来查看代码:

1
2
3
4
5
6
(Pdb) l
1 # pdbLearn.py
2 -> s = '0'
3 n = int(s)
4 print(10/n)
[EOF]

输入命令 n 可以单步执行代码,任何时候都可以输入命令 p 变量名 来查看变量::

1
2
3
4
5
6
7
8
9
10
11
12
(Pdb) n
> /Users/rooooooot/git/PythonLearning/Python3/liaoxuefeng/pdbLearn.py(3)<module>()
-> n = int(s)
(Pdb) p s
'0'
(Pdb) p n
*** NameError: name 'n' is not defined
(Pdb) n
> /Users/rooooooot/git/PythonLearning/Python3/liaoxuefeng/pdbLearn.py(4)<module>()
-> print(10/n)
(Pdb) p n
0

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

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 继续运行。

单元测试

单元测试的测试用例要覆盖常用的输入组合、边界条件和异常。

以测试为驱动的开发模式最大的好处就是确保一个程序模块的行为符合我们设计的测试用例。在将来修改的时候,可以极大程度地保证该模块行为仍然是正确的。

编写一个 Dict 类,这个类的行为和 dict 一致,但是可以通过属性来访问,用起来就像下面这样:

1
2
3
4
5
>>> d = Dict(a=1, b=2)
>>> d['a']
1
>>> d.a
1

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# mydict.py
class Dict(dict):
def __init__(self, **kw):
super().__init__(**kw)
def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError(r"'Dict' object has no attribute '%s'" % key)
def __setattr__(self, key, value):
self[key] = value

编写单元测试,需要引入 Python 自带的 unitteste 模块,编写 mydict_test.py 如下:

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
35
36
37
38
39
40
41
42
# mydict_test.py
import unittest
from mydict import Dict
class TestDict(unittest.TestCase):
def setUp(self):
print('setUp...')
def test_init(self):
d = Dict(a = 1, b = 'test')
self.assertEqual(d.a, 1)
self.assertEqual(d.b, 'test')
self.assertTrue(isinstance(d, dict))
def test_key(self):
d = Dict()
d['key'] = 'value'
self.assertEqual(d.key, 'value')
def test_attr(self):
d = Dict()
d.key = 'value'
self.assertTrue('key' in d)
self.assertEqual(d['key'], 'value')
def test_keyerror(self):
d = Dict()
with self.assertRaises(KeyError):
value = d['empty']
def test_attrerror(self):
d = Dict()
with self.assertRaises(AttributeError):
value = d.empty
def tearDown(self):
print('tearDown...')
if __name__ == '__main__':
unittest.main()

编写单元测试时,需要编写一个测试类,从 unittest.TestCase 继承。

test 开头的方法就是测试方法,不以 test 开头的方法不被认为是测试方法,测试的时候不会被执行。

对每一类测试都需要编写一个 test_xxx() 方法。由于 unittest.TestCase 提供了很多内置的条件判断,最常用的断言就是 assertEqual()

1
self.assertEqual(abs(-1), 1) # 断言函数返回的结果与1相等

另一种重要的断言就是期待抛出制定类型的 Error,比如通过 d['empty'] 访问不存在的 key 时,断言会抛出 keyError

1
2
with self.assertRaises(KeyError):
value = d['empty']

setUptearDown

文档测试

您的支持将鼓励我继续创作!

热评文章