一、变量与运算符

1.1 变量名与类型

Python 中所有数据都以对象的形式存在,对象就像一个盒子,盒子里面装着值,而变量名只是贴在这个盒子上的一个标签。

对象有不同的类型,类型决定了可以对它进行的操作。

对象的类型还决定了它装着的数据是允许被修改的变量(可变的)还是不可被修改的常量(不可变的)。

可变对象就像一个开着口的盒子,盒子中的值可以被修改,但无法改变盒子本身,即无法改变对象的类型。所以 Python 是强类型语言。

变量名只是一个标签,它可以被贴在任何类型的对象上。所以 Python 又被叫做动态类型语言。

1.2 变量的命名规则

  • 类名采用大驼峰命名法,即将类名中的每个单词的首字母都大写,其他小写,不使用下划线。

  • 实例名和模块名都采用小写格式,并在单词之间加上下划线。

  • 常量名使用全大写+下划线

1.3 对象的三个基本要素

Python 中对象包含三个基本要素:id(身份标识),typevalue

== 作用于 valuevalue 相等为 True,否则为 False

is(同一性运算符)作用于 id,即两个变量名本质上是不是同一个对象。如:

1
2
3
4
5
6
a = b = [1, 2, 3]
c = [1, 2, 3]

print(a == c) # output: True
print(a is c) # output: False
print(a is b) # output: True

1.4 Python 中的真值与假值

真值:除了假值都是真值

假值:

  • False(bool 类型)
  • None(null 类型)
  • 0(整型)
  • 0.0(浮点型)
  • ''(空字符串)
  • [](空列表)
  • ()(空元组)
  • {}(空字典)
  • set()(空集合)

假值不仅仅包含 None,所以要判断是否为 None,必须使用 is 运算符:

1
2
3
4
5
6
7
thing = None
if thing is None:
print('It's nothing')
else:
print('It's something')

# 结果:It's nothing

1.5 Python 中其他进制数字的表示

0b10 —— 二进制的10

0o10 —— 八进制的10

0x10 —— 十六进制的10

1.6 关于运算符

  1. 除法 / 更接近自然语言,整数相除不会将小数部分舍弃,若要舍弃,可以使用 floor除法(//)。floor除法即使是浮点数相除也会舍去小数部分。

  2. a ** b, 表示 a 的 b 次幂,幂运算操作符比其左侧的一元操作符优先级高,比其右侧的一元操作符优先级低。结合性:从右向左

  3. 逻辑运算符使用英文单词:and, or, not。优先级:not > and > or

  4. 写法 3 < 4 < 5 也是合法的,它其实被解释为 3 < 4 and 4 < 5

  5. 三元操作符:result = x if x > y else y,若 x > y 成立,将 x 赋给 result,若不成立,将 y 赋给 result

  6. 成员资格操作符 in :元素是否包含在序列中,若是,返回 True,否则 Falsenot in 相反。

  7. Python 中没有自增自减运算符(如 ++)。但有增量赋值运算符(如:+=

二、常用 BIF (Built-in functions)

  1. 查看有哪些 BIF:dir(__builtins__),显示出的纯小写的就是 BIF。

  2. ord(char):将一个字符转换为对应的 ASCII 码值或 Unicode 值。

  3. chr(num):将一个 ASCII 码值或 Unicode 值转换为字符。

  4. del 变量名:删除某个变量。

  5. type(变量名):判断变量类型。

  6. isinstance(数据, 数据类型):验证数据是否为指定类型

  7. range(start,end,step):产生一个可迭代对象,包含 start ~ end-1 步进为 step 的所有自然数。若传一个参数,则传给 end,两个参数传给 start 和 end。可以用 range 产生递减序列:range(10, 0, -1)

  8. id(对象):每个对象有唯一的 ID 值,该函数获取对象的 ID。

  9. zip(序列1, 序列2):将两个序列打包成 zip 对象,这个对象相当于一个双值子序列,元素一一对应,可用 dict() 将该对象转化为字典。如果两序列元素个数不等,打包后的元素个数以少的为准。

  10. filter(函数或 None, 可迭代对象):如果参数为 None,则将可迭代对象中值为 True 的值打包成一个对象返回,可以用 list() 将这个对象转化为列表;如果参数为一个函数,则将可迭代对象中的值作为参数调用函数,将返回值为 True 的值打包成一个对象返回。

  11. map():map 是映射的意思。该函数与 filter 有一样的形参,作用是将可迭代对象中的每个值作为参数代入函数中,将返回值打包成一个对象返回。作用是将序列中每个元素均执行某个操作。

  12. 工厂函数:list(), tuple(), str(), dict(), set()等。

  13. 内置数学函数:

    • abs():取绝对值

    • round(number[, 保留的小数位数,默认为0]):保留指定位小数

    • divmod(被除数,除数):结果是一个元组:(商,余数)

  14. any():参数为一个列表,当列表中的元素只要有一个不为 假值 时,返回 True,否则返回 False

  15. all():参数为一个列表,当列表中的所有元素都不为 假值 时,返回 True,否则返回 False

  16. locals():返回当前作用域中的变量集合。

三、流程控制

Python 没有 do-whileswitch 语句。

Python 不允许 ifelse 等关键词后出现赋值号,出现时会报错,这有效的防止开发者错把 == 写成 =

for循环:

1
2
for 变量 in 可迭代对象:
循环体

文件对象也是可迭代对象,可以用for循环打印出每一行。

while-elsefor-else

1
2
3
4
5
6
7
8
tu = (1, 2, 3, 4, 5)
i = 0
while i < len(tu):
if tu[i] == 3:
break
i += 1
else:
print('tu 中不存在 3')

如果 while 循环正常结束(没有使用 break 跳出),则会执行 else 语句块。否则不执行 else 语句块。

for 循环用法类似。

四、序列

序列是几种数据类型的统称,列表元组字符串都是一种序列类型。序列类型都有一些通用的操作,例如:解包分片重复操作符 *连接操作符 +for 循环遍历 等,还有一些通用的 BIF。

4.1 序列通用操作

  1. 使用中括号和索引取值

    1
    2
    3
    string = 'string'
    print(string[0]) # output: s
    print(string[-3]) # 支持负数索引
  2. 解包:

    1
    2
    3
    li = [1, 2, 3]
    a, b, c = li
    print(a, b, c) # output: 1 2 3
  3. 分片:

    • 序列名[start:end]:返回一个下标从 startend - 1 的序列,原序列不变。

    • 序列名[:end]:返回一个下标从 0end - 1 的序列。

    • 序列名[:]:获得一个序列的拷贝。

    • 序列名[start:end:step]:从 start 切到 end - 1 ,每 step 提取一个。

    • 序列名[::-1]:序列逆序。

    • startendstep 都支持负数。

  4. 使用工厂函数实现列表、元组、字符串的相互转换:list(), tuple(), str()

  5. for循环遍历序列:

    1
    2
    for var1, var2 in [[1, 2], [3, 4], [5, 6]]:		# 每个子列表被解包后赋值给 var1 和 var2
    # some code

使用 zip() 函数并行迭代多个序列:

1
2
3
4
5
days = ['Monday', 'Tuesday', 'Wednesday']
fruits = ['banana', 'orange', 'peach']
drinks = ['coffee', 'tea', 'beer']
for day, fruit, drink in zip(days, fruits, drinks):
print(day + ' ' + fruit + ' ' + drink)
  1. 序列通用 BIF:

    • len(sequence):求序列长度

    • max(sequence):返回序列中的最大值

    • min(sequence):返回序列中的最小值

    • sum(sequence, [num]):求序列和,若加了可选参数 num,则将 num 也加入其中

    • sorted(sequence, [reverse = false]):将序列排序(临时排序)

    • reversed(sequence):将序列逆转(临时逆序),返回一个可迭代对象。可以 list(reversed(sequence)) 将可迭代对象转换为列表

    • enumerate(sequence):将序列中每个元素分别与从 0 开始的数合成一个元组,返回一个对象,可以将对象转换成列表:

      1
      2
      3
      string = '123456789'
      li = list(enumerate(string))
      print(li) # output: [(0, '1'), (1, '2'), (2, '3'), (3, '4'), (4, '5'), (5, '6'), (6, '7'), (7, '8'), (8, '9')]
  2. 使用 in 操作符判断元素是否在序列中:var_name in sequence_name,返回值为 TrueFalse

4.2 列表 (list)

  1. 向列表添加元素:

    • 在末尾添加一个元素:列表名.append(元素)

    • 用一个列表扩展另一个列表(可以实现向列表中插入多个元素):列表名.extend(列表名)

    • 在指定位置前插入一个元素:列表名.insert(位置,元素)

  2. 删除列表元素:

    • 列表名.remove(元素):根据元素的值进行删除操作

    • del 列表名[n]:删除列表中第 n+1 个元素

    • 列表名.pop([index]):删除并返回指定位置上的元素,默认是最后一个元素

  3. 列表逆序(原地逆序):列表名.reverse()

  4. 列表排序(原地排序):列表名.sort(reverse=False)reverse 参数为 False 代表从小到大排序,为 True 代表从大到小排序。默认为 False

  5. 列表名.count(元素):计算该元素出现了几次。

  6. 列表名.index(元素[, start, end]):返回区间内该元素所在位置的下标,若有多个,只返回第一个的下标

  7. 列表推导式:

    1
    2
    squares = [value**2 for value in range(1,11) if value%2==0]
    # 列表名 表达式 for循环为表达式提供值 if语句控制哪些项可以增加到新列表中

    列表推导式中还可以写多个 for,且每个 for 都可以有自己的 if

    1
    2
    3
    4
    5
    rows = range(1,4)
    cols = range(1,3)
    cells = [(row, col) for row in rows for col in cols]
    for row, col in cells:
    print(row, col)

4.3 元组 (tuple)

  1. 关于元组:

    • 元组是带了枷锁的列表,创建后其值不能改变。

    • 列表的特点是 [],而元组的特点是 ,(), 更重要。

    • 元组占用的空间比列表小。

    • 函数的参数和返回值是以元组形式传递的。

      可以这样赋值:x, y, z = 6, 5, 4 ,这实际上是创建元组再解包元组。交换两个数:x, y = y, x,这也是创建元组再解包元组。

  2. 创建元组:

    • 创建空元组:tuple1 = ()

    • 创建普通元组:tuple2 = 1,tuple2 = (1,), 不能是 tuple2 = (1), 这样会是整数(必须要有逗号)。

4.4 字符串 (str)

Python 中的字符串是常量,创建后无法改变。

4.4.1 原始字符串

原始字符串(raw string)是指不包含转义符的字符串,即原始字符串中的转义符 \ 没有了转义功能。

原始字符串最后不能有反斜杠,如果非要输入反斜杠,可以用字符串拼接的方式:

1
string = r'C:\Program Files\FishC\Good' + '\\'

4.4.2 模板字符串

当字符串的某个部分是个变量时,可能需要用拼接的方式定义这个字符串,如:

1
2
number = 10
string = 'I love you ' + str(number * 10000) + ' years'

虽然这种方式确实能达到目的,但如果该字符串需要的变量非常多(比如 sql 语句中),这种方式会让人眼花缭乱,可读性很差。

此时模板字符串是个不错的选择,同样是上面那个例子,用模板字符串写会使字符串的结构清晰明了:

1
2
number = 10
string = f'I love you {number * 10000} years' # 引号前加 f 表示这是一个模板字符串。表达式用大括号包起来

实际上下面的 %format 两种方式也能达到目的。相比之下,模板字符串的结构最清晰,但缺点是无法对变量做具体的格式化控制。

4.4.3 使用格式控制符 % 格式化字符串

1
2
string = 'I love you %d years!'
print(string %100000) # output: I love you 100000 years!

要格式化多个元素,要将元素作为元组,如:'I love %s %d years!' %('you', 100000)

常用字符串格式化符号:

符号 含义
%c 将字符及其 ASCCI 码格式化为字符串
%s 将字符串格式化为字符串
%d 将整数格式化为字符串
%o 将无符号八进制数格式化为字符串
%x 将无符号十六进制数格式化为字符串(10~16为小写字母)
%X 将无符号十六进制数格式化为字符串(10~16为大写字母)
%f 将浮点数格式化为字符串。%10.1f 表示 共 10 位,小数 1 位

格式化控制符辅助指令:

符号 含义
- 左对齐。如 "%-10.1f"%1.56 –> '1.6 '
+ 在正数前显示 +
# 显示八进制的 0,十六进制的 0x
0 填充为 0,而不是空格

4.4.4 使用内置方法 format() 格式化字符串

format() 有两种参数:位置参数关键字参数。该方法的作用是将参数传给 replacement字段(replacement字段用 {} 表示)。

  • 传位置参数时,将字符串中的 {数字} 替换成对应的参数(数字要从 0 开始),如:"{0} love {1}!".format('I', 'you') –> I love you!。也可以不写数字:'{},{}'.format('chuhao',20)。还可以这样用:'{1},{0},{1}'.format('chuhao',20)

  • 传关键字参数:"{关键字1} love {关键字2}!".format(关键字1 = 'I', 关键字2 = 'you') –> I love you!。传关键字参数的好处是可以不在意顺序。

  • 位置参数和关键字参数可以混用,但位置参数要放在前面。

\\ 转义成 \ 一样,'0' 外层大括号会把里面的大括号转义,即 '0'.format(1) –> {0}

字符串的填充与对齐:(冒号为引导符号)

  • '{:>8}'.format('189'):右对齐,以英文的空格填充,总共占 8 位

  • '{:0<8}'.format('189'):左对齐,以 0 填充,总共占 8 位

  • '{:^8}'.format('189'):居中对齐

如果输出的是中文,用空格填充时的结果并不如意,因为填充的空格是英文空格,英文空格要比中文空格短。UTF-8 编码的中文空格可以用 chr(12288) 表示。

浮点数保留指定小数位:'{:.2f}'.format(321.33345)(保留两位小数)

整数和小数的千位分隔符:'{:,}'.format(1234567890) –> 1,234,567,890

4.4.5 字符串常用内置方法

  1. 最常用:

    方法 功能
    replace(old, new[, count]) 把字符串中的 old 子串替换成 new 子串,如果 count 指定,则替换不超过 count 次,默认为全部替换。返回替换后的字符串。
    encode(‘utf-8’) 将字符串以指定的编码方式编码成二进制码,默认为 UTF-8。
    decode(‘utf-8’) 将二进制码解码为字符串,参数为二进制码的编码方式。
    split(‘,’ maxsplit=-1) 不带参数默认是以空格为分隔符分片字符串,如果 maxsplit 参数有设置,则仅使用 maxsplit 个分隔符分割子字符串,返回分片后的子字符串拼接的列表。maxsplit=-1表示尽可能多的分割
    ‘,’.join(列表) 将列表中的字符串连接起来,中间以 , 分隔
  1. 查找子串:

    方法 功能
    find(sub[, start[, end]]) 检测 sub 是否包含在字符串中,如果有则返回索引值,否则返回 -1startend 参数表示范围。
    index(sub[, start[, end]]) find() 方法一样,不过如果 sub 不在字符串中会产生一个异常。
    rfind(sub[, start[, end]]) 类似于 find() 方法,不过是从右边开始查找。
    rindex(sub[, start[, end]]) 类似于 index() 方法,不过是从右边开始。
    partition(sub) 找到子字符串 sub,把字符串分成一个 三元组 (pre_sub, sub, fol_sub),如果字符串中不包含 sub 则返回 ('原字符串', '', '')
    rpartition(sub) 类似于 partition() 方法,不过是从右边开始查找。
  2. 大小写转换

    方法 功能
    title() 返回标题化(所有的单词都是以大写开始,其余字母均小写)的字符串。
    capitalize() 把字符串的第一个字符改为大写,其他改为小写。
    casefold() 把整个字符串的所有字符改为小写(适用于任何语言)
    upper() 转换字符串中的所有小写字符为大写。
    lower() 转换字符串中所有大写字符为小写。(适用于 ASCII,所以只能用于英语或没有大小写区分的语言)
    swapcase() 翻转字符串中的大小写。
  3. 处理空白

    方法 功能
    rstrip() 删除字符串末尾的空格。
    lstrip() 去掉字符串左边的所有空格
    strip([chars]) 删除字符串首尾所有的空格,chars 参数可以定制删除的字符。
    expandtabs([tabsize=8]) 把字符串中的制表符转换为空格,若不指定参数,默认的空格数是 tabsize=8
  4. 填充

    方法 功能
    center(width) 将字符串居中,并使用空格填充至长度 width 的新字符串
    ljust(width) 返回一个左对齐的字符串,并使用空格填充至长度为 width 的新字符串。
    rjust(width) 返回一个右对齐的字符串,并使用空格填充至长度为 width 的新字符串。
    zfill(width) 返回长度为 width 的字符串,原字符串右对齐,前边用 0 填充。
  5. 检查字符

    方法 功能
    startswith(prefix[, start[, end]]) 检查字符串是否以 prefix 开头,是则返回 True,否则返回 False
    endswith(sub[, start[, end]]) 检查字符串是否以 sub 子字符串结束,如果是返回 True,否则返回 False
  6. 判断类型

    方法 功能
    isalnum() 如果字符串至少有一个字符并且所有字符都是字母或数字则返回 True,否则返回 False
    isalpha() 如果字符串至少有一个字符并且所有字符都是字母则返回 True,否则返回 False
    isdecimal() 如果字符串只包含十进制数字则返回 True,否则返回 False
    isdigit() 如果字符串只包含数字则返回 True,否则返回 False
    islower() 如果字符串中至少包含一个区分大小写的字符,并且这些字符都是小写,则返回 True,否则返回 False
    isnumeric() 如果字符串中只包含数字字符,则返回 True,否则返回 False
    isspace() 如果字符串中只包含空格,则返回 True,否则返回 False
    istitle() 如果字符串是标题化(所有的单词都是以大写开始,其余字母均小写),则返回 True,否则返回 False
    isupper() 如果字符串中至少包含一个区分大小写的字符,并且这些字符都是大写,则返回 True,否则返回 False

五、字典

5.1 关于字典

  • 字典是通过键来索引的。

  • 因为字典按键索引,所以无所谓顺序。

  • 字典的键不能重复。

  • 可以通过键改变该键对应的值,如果键不存在,则在字典最后创建这个键值对。

  • 字典与序列一样,使用赋值号直接赋值只是创建了一个引用。

  • 字典的键与值都可以是一个函数(Python 字典比 JavaScript 的对象还要强大)

  • 只有不可变类型的变量可以作为字典的键,举个例子:元组和字符串可以作为字典的键,但列表不可以。

  • 判断某个键是否存在:键 in 字典

5.2 创建字典

1
字典名 = {key1: value1, key2: value2, key3: value3}

1
2
3
li = [[1, 2], [3, 4], [5, 6]]		# 这种序列叫做 双值子序列
d = dict(li)
print(d) # output: {1: 2, 3: 4, 5: 6}

可以对任何包含双值子序列的序列使用 dict()

其他双值子序列的例子:

  • [ ('a', 'b'), ('c', 'd'), ('e', 'f') ]

  • ( ['a', 'b'], ['c', 'd'], ['e', 'f'] )

  • [ 'ab', 'cd', 'ef' ]

  • [[1, 2], (3, 4), '56']

使用 zip() 函数可以很方便地创建双值子序列。

5.3 字典常用内置方法

字典名.get(key):功能与 []索引相似,只是用 [] 获取不存在的键会抛出异常,但若使用 get 方法,当 key 不存在于字典中时,或返回 None

字典名.keys():返回一个对象,里面包含该字典所有的键

字典名.values():返回一个对象,里面包含该字典所有的值

字典名.items():返回一个对象,里面包含该字典所有的键和值,可以用 list() 转换成 双值子序列

字典2 = 字典1.copy():复制字典

字典1.update(字典2):将 字典2 合并到 字典1,如果有相同的键,新值会覆盖原来的值

字典名.fromkeys(列表或元组[,值]),值默认为 None。该方法的用法:先创建一个空字典,配合 range() 为字典添加元素。

字典名.clear():清除所有元素

5.4 字典推导式

语法与列表推导式相似,只是表达式是一个键值对,且使用大括号,下面是一个简单的例子

1
2
dic = {num:num for num in range(3)}
print(dic) # output: {0: 0, 1: 1, 2: 2}

对单词中的字母计数,并返回字典形式的计数结果:

1
2
3
word = 'letters'
letter_counts = {letter: word.count(letter) for letter in set(word)}
print(letter_counts) # output: {'t': 2, 'l': 1, 'e': 2, 'r': 1, 's': 1}

六、集合

大括号既可表示字典,也可表示集合,当 {} 内的元素没有映射关系时表示集合。

集合就像只有键的字典,所以集合中不会出现重复的元素。

集合可用于去除重复元素。

可以使用 in 判断一个值是否在集合中。这是集合最常用的操作。

  1. 创建集合:

    1
    2
    3
    set1 = {12345}
    set2 = set(object) # object 是一个序列,或一个集合。
    set3 = set() # 创建空集合。不能是 set3 = {},这样创建的是字典
  2. 工厂函数 set()

    • set(序列):将序列转化为集合

    • set(字典):将字典转化为集合(只有键会被使用)

  3. 常用集合运算:

运算 含义
s & t 求交集
`s t`
s - t 求差集
s ^ t 求异或
s <= t 判断 s 是否是 t 的子集
s < t 判断 s 是否是 t 的真子集
s >= t 判断 s 是否是 t 的超集
s > t 判断 s 是否是 t 的真超集
  1. 集合推导式:

语法与列表推导式相同,只是使用大括号,且推导结果无重复值

1
2
s = {i for i in [1, 2, 3, 1, 2, 3]}
print(s) # output: {1, 2, 3}

七、生成器、迭代器、可迭代对象

  1. 生成器:

    要使用一个序列,需要将这个序列存入内存中,如果这个序列很大,显然会很消耗内存。

    生成器是一种可迭代对象,只能通过 for 循环或 next() 函数来顺序取出它保存的值。

    迭代一个生成器时,在内存中保存的是上一次调用的位置以及推算下一个值的算法,所以生成器对内存的消耗很少。

    定义一个生成器有两种方法:

    1. 生成器推导式:gen = (i for i in range(10))。语法与列表推导式基本相同,只是使用小括号

    2. 生成器函数:

      1
      2
      3
      4
      5
      def my_range(start, end, step):			# 这是一个生成器函数
      for i in range(start, end, step):
      yield i

      gen = my_range(0, 10, 1) # gen 是一个生成器

      生成器函数和普通函数类似,但是它的返回值使用 yield 语句声明而不是 return

  2. 可迭代对象:

    可以用 for 循环迭代的对象叫做可迭代对象(例如:序列,字典,集合,生成器)

  3. 迭代器:TODO

八、函数

8.1 函数基础

  1. 文档字符串:自定义函数时可以书写文档字符串(一串紧跟在函数头部后的字符串),作为函数的说明文档,调用 help() 可以显示此说明文档,函数名.__doc__ 可以得到此文档字符串。

  2. 参数:

    1. 位置参数

    2. 关键字参数(使用关键字参数的方式调用函数可以不在意参数的顺序)

      1
      2
      3
      4
      def woof(name, word):
      print(name + ' says: ' + word)

      woof(word='woof woof!', name='wolf') # wolf says: woof woof!

      可以把位置参数和关键字参数混合使用,此时应该将位置参数写在前面

    3. 默认参数

      语法与 C++ 语言相似。

      不要将可变类型作为默认参数值,因为默认参数是在函数定义时就计算好的,而不是函数运行时。

      下面的例子将列表这种可变类型作为默认参数值,导致每次调用该函数时的默认参数不一样:

      1
      2
      3
      4
      5
      6
      def buggy(arg, result=[]):
      result.append(arg)
      print(result)

      buggy('a') # output: ['a']
      buggy('b') # output: ['a', 'b']
    4. 收集参数

      • 使用 * 收集位置参数

        1
        2
        3
        def print_args(*args):
        for argv in args:
        print(argv)

        该函数可以接收任意数量的位置参数,这些参数被打包成一个元组传给 args

        如果函数中还需要确定的位置参数,需要将这些位置参数写在前面,args 会收集剩下的参数

      • 使用 ** 收集关键字参数

        1
        2
        def print_kwargs(**kwargs):
        print(kwargs)

        该函数可以接收任意数量的关键字参数,这些参数被打包成一个字典传给 kwargs

  3. 返回值:Python 中数据类型很灵活,所以无需显式指定返回值。没有指定返回值的函数默认返回 None

  4. 关于全局变量:不能在函数体内修改 全局变量,因为这样 Python 会定义一个同名的 局部变量。(这是Python为保护全局变量不被意外修改而设置的屏蔽机制)。若要在函数体内修改全局变量,需要在修改之前 加一个声明 global 变量名

8.2 高级特性

  1. 函数是一等公民:Python 函数可以作为函数的参数、返回值,列表、元组、集合和字典的元素,甚至可以作为字典的键。

  2. 匿名函数:

    1
    func = lambda 形参列表 : 函数体		# 使用 lambda 表达式定义一个匿名函数并赋值给 func

    lambda 表达式的作用:

    • 当实现一些比较简单的功能时 lambda 表达式可以使代码更精简。

    • 不用为函数起名字。

    • 当被赋值的变量名被赋以其他值时,lambda 的内存会被回收。

  3. 内部函数与闭包:

    • 内部函数中也不能修改 外部函数 中定义的变量,与全局变量和局部变量的关系相似。同样的,可以通过使用 nonlocal 关键字(与 global 关键字使用方法相同)声明。

    • Python 中也有闭包的概念。

  4. 装饰器:

    详细内容可参考:https://foofish.net/python-decorator.html

    如果需要不修改函数的定义,而为函数添加新的功能,就需要用到装饰器。

    本质上,装饰器是一个返回函数的高阶函数

    现在假设有这样一个函数:

    1
    2
    def hello_world():
    print('Hello world!')

    现在需要在调用该函数前打印调用日志,所以定义一个装饰器来实现这个功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
	import functools

def log(func): # 这个 log 就是装饰器。一般装饰器的参数都是 func
@functools.wraps(func) # Python 内置装饰器,用来把 func 的 __name__ 等属性赋值给 wrapper,防止有些依赖函数签名的代码执行出错。当然,如果 log 中没有涉及到 __name__ 等,就不需要这一行代码
def wrapper(*args, **kwargs): # 装饰器内部的函数的参数一般都是 *args, **kwargs
print('call function %s' % func.__name__)
return func(*args, **kwargs)
return wrapper

@log # 这里使用装饰器 log 来装饰 hello_world 函数。这一句实际上相当于 hello_world = log(hello_world)
def hello_world():
print('Hello world!')

hello_world()
# 结果:
# call function hello_world
# Hello world!
装饰器的大体架构一般都是如上面那样写。

如果需要带参数的装饰器,需要编写一个返回装饰器的高阶函数,所以需要三层函数嵌套:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
	import functools

def log(text): # 这是一个返回装饰器的函数
def decorator(func): # 这是装饰器
@functools.wraps(func)
def wrapper(*args, **kwargs):
print('%s %s():' % (text, func.__name__))
return func(*args, **kwargs)
return wrapper
return decorator

@log('execute') # 这里相当于 hello_world = log('execute')(hello_world)
def hello_world():
print('Hello world!')

hello_world()
# 结果:
# execute hello_world()
# Hello_world!
这种装饰器的结构一般也都是这样写。

装饰器的惯常用法是把函数注册为事件处理程序,在特定事件发生时调用它。

九、类和对象

类中的函数通常称为 方法(method),数据称为 属性(attribute),对象是类的实例。

其实 Python 内置的工厂函数就是类。自定义类就相当于自定义工厂函数。

类和对象的作用:

  • 很多时候 Python 自带的数据结构不能满足需求,此时就可以通过类来创建自定义的数据结构。

  • 使用类可以将函数与数据打包在一起,因为很多函数只是针对特定格式的数据才能起作用,使用类将它们封装起来可以体现它们之间的关系。

  • 使用类可以划分代码的层次结构,使代码结构更清晰。

9.1 类的定义

1
2
3
4
5
6
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def eat():
# some code

__init__() 方法相当于 C++ 中的构造函数,当创建实例时会自动调用

self 相当于 this 指针,指向当前创建的实例。Python 中类的每个方法的第一个参数都必须是 self(不同于 C++,C++可以省略 this 指针)。

属性可以在 __init__() 方法内通过 self.attr = value 初始化,也可以在方法外通过 attr = value 初始化

在方法中修改属性的值需要用 self.attr = value 的方式,如果只是 attr = value,只会在该方法中创建一个同名的变量

在函数定义前加 @property 可以将该函数声明为一个属性,访问时不再需要加上括号。有些方法不会改变属性的值,只是返回属性的值,这中函数表现得就像一个属性,所以可以为它加上 @property

9.2 继承与派生

1
2
3
4
5
class Athlete(list):		# 继承工厂函数 list 的所有方法
def __init__(self):
super().__init__([]) # 调用父类的 __init__() 方法来初始化从 list 类派生的数据

# 继承了 list 类后,完全可以将 Athlete 的实例当做列表来使用,在任何能使用列表的地方都可以使用它。

子类可以重写父类的方法,也可以添加属性和方法。

子类重写某个方法后,如果需要调用父类的同名方法,可以使用 super() 函数,该函数用来获取父类的定义。比如上面这段代码中,子类重写了父类的 __init__() 方法,在创建对象时,父类的 __init__() 方法不会被自动调用,只能用 super() 函数显式调用。

如果子类没有定义 __init__() 方法,Python 会自动调用父类的 __init__() 方法来初始化。

9.3 属性的访问和设置

Python 类的属性没有公有和私有之分,使用时全凭自觉,但可以使用一些措施来防止私有属性被不小心更改。

Python 对那些需要刻意隐藏在类内部的属性有自己的命名规范:由连续的两个下划线开头(如:__name),这样命名的属性会被自动加上前缀 _类名(如:_Person__name),从而起到隐藏的效果。这被叫做名称重整

使用名称重整加上下面的两种方法,可以在一定程度上达到隐藏私有属性的目的。

  • 使用 property() 函数设置 gettersetter

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Person():
    def __init__(self, name):
    self.__name = name # 只有从外部调用 __name 时名称才会被重整为 _Person__name,内部调用时仍写 __name
    def get_name(self): # __name 属性的 getter
    return self.__name
    def set_name(self, name): # __name 属性的 setter
    self.__name = name

    name = property(get_name, set_name) # property() 的第一个参数是 getter,第二个参数是 setter

    现在,真正的 __name 被隐藏,暴露出来的是 name 属性。

    当访问 name 时,会调用它的 getter(也就是 get_name 函数);当给 name 赋值时,会调用它的 setter(也就是 set_name 函数)

    但是 get_name()set_name() 可以被显式调用

    1
    2
    3
    4
    ma = Person('ma')
    print(ma.name) # output: ma
    ma.name = 'm'
    print(ma.get_name()) # output: m 这里显式调用了 __name 属性的 getter
  • 使用 @property@func.setter 设置 gettersetter

    这种方法与上一种方法的原理类似,只是设置 gettersetter 的方法不同

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Person():
    def __init__(self, name):
    self.__name = name
    @property # 将 name 方法注册为属性。这里就相当于设置了 getter
    def name(self):
    return self.__name
    @name.setter # 为 name 方法设置 setter
    def name(self, name):
    self.__name = name

    可以看到,gettersetter 的函数名都是 name,所以,这种方法可以避免显式调用 gettersetter

9.4 方法的类型

类中可以定义三种方法:

  • 对象的方法:

    最常用。上面代码中的定义的方法都是对象的方法。

    对象的方法第一个参数都是 self。这些方法虽然是所有对象共用的,但理论上属于对象。

    对象的方法会作用于某个对象(比如,某个方法会改变某个属性,这只会作用于指定的对象,而不会对其他对象产生影响)。

  • 类的方法:

    用前缀修饰符 @classmethod 指定的方法都是类方法。

    用类方法改变类会对类的所有实例产生影响。

    类方法的第一个参数都是 cls(class 的简称。这是约定的名字,并不是必须的名字),代表类本身。

    即使不创建对象,类方法也可以被调用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class A():
    count = 0
    def __init__(self):
    A.count += 1
    @classmethod
    def kids(cls):
    print("A has", cls.count, "objects.")

    a1 = A()
    a2 = A()
    a3 = A()

    A.kids() # output: A has 3 objects.
  • 静态方法:

    静态方法既不会影响类也不会影响类的对象。它们出现在类的定义中仅仅是为了方便,否则它们只能孤零零地出现在代码的其他地方,这会影响代码的逻辑性。

    用前缀修饰符 @staticmethod 指定的方法都是静态方法。

    静态方法既不需要 self,也不需要 cls

9.5 多态

Python 对实现多态要求得十分宽松,这意味着可以对不同对象调用同名的操作,甚至不用管这些对象的类型是什么。

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
class Quote():
def __init__(self, person, words):
self.person = person
self.words = words
def who(self):
return self.person
def says(self):
return self.words + '.'

class QuestionQuote(Quote):
def says(self):
return self.words + '?'

class ExclamationQuote(Quote):
def says(self):
return self.words + '!'

hunter = Quote('Elmer Fudd', "I'm hunting wabbits")
print(hunter.who(), 'says:', hunter.says()) # Elmer Fudd says: I'm hunting wabbits.

hunted1 = QuestionQuote('Bugs Bunny', "What's up, doc")
print(hunted1.who(), 'says:', hunted1.says()) # Bugs Bunny says: What's up, doc?

hunted2 = ExclamationQuote('Daffy Duck', "It's rabbit season")
print(hunted2.who(), 'says:', hunted2.says()) # Daffy Duck says: It's rabbit season!

9.6 魔法方法

类中有些方法会有特殊的用途,这些方法都以 双下划线(__) 开头和结尾,被叫做魔法方法。

__init__ 就是一个魔法方法。

下面举个栗子:

1
2
3
4
5
6
7
8
9
10
class Plural():			# 定义一个复数对象
def __init__(self, real, imag):
self.real = real
self.imag = imag
def __eq__(self, other): # 定义 __eq__ 方法(相当于 C++ 中重载了 == 运算符)
return self.real == other.real and self.imag == other.imag # 如果实部和虚部都相等,返回 True

p1 = Plural(1, 1)
p2 = Plural(1, 2)
print(p1 == p2) # output: False

这样的魔法方法还有很多:

  • 和比较运算符相关的魔法方法:

    方法 含义
    __eq__(self, other) 定义 == 的行为
    __ne__(self, other) 定义 != 的行为
    __lt__(self, other) 定义 < 的行为
    __le__(self, other) 定义 <= 的行为
    __gt__(self, other) 定义 > 的行为
    __ge__(self, other) 定义 >= 的行为
  • 和算数运算符相关的魔法方法:

    方法 含义
    __add__(self, other) 定义 + 的行为
    __sub__(self, other) 定义 - 的行为
    __mul__(self, other) 定义 * 的行为
    __truediv__(self, other) 定义 / 的行为
    __floordiv__(self, other) 定义 // 的行为
    __mod__(self, other) 定义 % 的行为
    __divmod__(self, other) 定义当被 divmod() 调用时的行为
    __pow__(self, other[, modulo]) 定义当被 power() 调用或 ** 运算时的行为
    __radd__(self, other) (与上方相同,当左操作数不支持相应的操作时被调用)
    __rsub__(self, other) (与上方相同,当左操作数不支持相应的操作时被调用)
    __rmul__(self, other) (与上方相同,当左操作数不支持相应的操作时被调用)
    __rtruediv__(self, other) (与上方相同,当左操作数不支持相应的操作时被调用)
    __rfloordiv__(self, other) (与上方相同,当左操作数不支持相应的操作时被调用)
    __rmod__(self, other) (与上方相同,当左操作数不支持相应的操作时被调用)
    __rdivmod__(self, other) (与上方相同,当左操作数不支持相应的操作时被调用)
    __rpow__(self, other) (与上方相同,当左操作数不支持相应的操作时被调用)
  • 与增量赋值运算符相关的魔法方法:

    方法 含义
    __iadd__(self, other) 定义 += 的行为
    __isub__(self, other) 定义 -= 的行为
    __imul__(self, other) 定义 *= 的行为
    __itruediv__(self, other) 定义 /= 的行为
    __ifloordiv__(self, other) 定义 //= 的行为
    __imod__(self, other) 定义 %= 的行为
    __ipow__(self, other[, modulo]) 定义 **= 的行为
  • 其他常用魔法方法:

    方法 含义
    __init__(self[, ...]) 相当于构造函数
    __del__(self) 相当于析构函数
    __str__(self) 定义当被 str() 调用时的行为
    __bool__(self) 定义当被 bool() 调用时的行为,应该返回 TrueFalse

十、命名空间、模块与包

一个 .py 文件就是一个模块。每个模块都有自己的命名空间,模块名就是命名空间名。

使用命名空间可以防止变量名冲突,因为不同命名空间里定义的相同变量名不会发生冲突。

当前模块的命名空间:__main__

模块中的 类、函数、变量 等可以在其他模块中导入并使用。

导入模块的三种方法:

  • import pickle。调用方法:pickle.dump()。缺点:若模块名很长,写起来会比较麻烦

  • from pickle import dump。调用方法:dump()。缺点:这种导入方法会将 pickle 命名空间的 dump 导入当前命名空间,如果当前命名空间也有一个叫 dump 的变量,会发生命名冲突

  • import pickle as p。调用方法:p.dump()。缺点:改变了模块名,他人维护困难

注意:这里的 pickle 要理解为命名空间名,而不是模块名,虽然命名空间名和模块名相同。

包是包含多个模块的一个目录。从包中导入模块:from package_name import module_name

包中需要添加一个 __init__.py 文件,Python 以此来确定该目录是一个包。

  • __init__.py 文件中可以不写任何代码,也可以写。__init__.py 文件会在包或包中的任何东西被导入时执行
  • 如果直接从包导入变量,如:from package_name import var_name,只能导入定义在 __init__.py 文件中的变量。要导入包中其他文件中定义的变量,可以这样导入:from package_name.module_name import var_name

模块搜索路径:

  • 导入模块或包时,Python 会在 模块搜索路径 中搜索要导入的模块。

  • sys.path 是一个列表,记录着所有的 模块搜索路径,越靠前的搜索路径优先级越高

从相对路径导包:

  • . 表示当前包
  • .. 表示上一级的包
  • from . import module_name 表示从当前包导入 module_name 模块。
  • from ..main import module_name 表示从上一级目录的 main 包导入 module_name 模块。
  • from .main.module_name import var_name 表示从当前目录的 main 包的 module_name 模块中导入 var_name

模块被别的模块导入时,会在模块所在目录下产生一个 __pycache__ 目录,里面存储模块转换成的 pyc 文件。

pyc 文件是 py 文件转换成的中间字节码文件,如果程序在下次运行前没有改变,则会运行 pyc 文件,提高速度。

十一、异常处理

使用 if-else 避免错误要考虑到所有可能出错的情况,这样写出来的程序很脆弱。且当错误情况很多时,会导致程序很复杂,甚至处理异常的代码比主程序还要多,其他开发者并不知道这些 if-else 是用来处理异常的,严重降低可读性。

使用 try-except 可以处理所有错误,不用考虑有哪些错误情况,写出来的程序较健壮。且可以避免向程序增加很多不必要的代码和逻辑。

if-else 是避免错误;try-except 是让错误发生然后处理错误。

文件操作易出错,所以经常搭配 try-except 来使用。

  1. 异常处理语句:

    1
    2
    3
    4
    5
    6
    7
    8
    try:				# 当 try 语句块中的语句抛出异常时,执行 except
    ...
    except 异常名 as reason: # reason 是一个变量,接收 发生异常的原因
    ...
    else: # else 语句块在 try 语句块没有抛出异常时执行
    ...
    finally: # finally 语句块为必定执行的语句
    ...
  2. 断言(assert 语句):

    若语句为真,则不做任何处理;若语句为假,抛出异常 AssertionError

    当需要确保程序中的某个条件一定为真才能让程序正常工作的话,assert 语句就非常有用了。

  3. 手动抛出异常:raise 异常名(word)word 是一个字符串,一般写异常发生的原因

  4. 编写自己的异常:

    异常其实是一个类,编写异常就是编写一个类

    1
    2
    3
    4
    5
    6
    7
    class UppercaseException(Exception):		# 这里继承 Python 自带的 Exception 类,所以即使没有为这个异常编写任何代码,它仍然具有异常特有的一些特性
    pass

    words = ['eeenie', 'meenie', 'miny', 'MO']
    for word in words:
    if word.isupper(): # 当 word 中有大写字母时
    raise UppercaseException('存在大写字母') # 用 raise 语句抛出异常
  5. Python 常见标准异常:

    异常 原因
    SyntaxError Python 的语法错误
    IndentationError 缩进错误
    TabError Tab 和空格混合使用
    NameError 尝试访问一个不存在的变量
    TypeError 不同类型间的无效操作
    OverflowError 数值运算超出最大限制
    ZeroDivisionError 除数为零
    FloatingPointError 浮点计算错误
    IndexError 索引超出序列的范围
    ValueError 传入无效的参数
    AttributeError 尝试访问未知的对象属性
    KeyError 字典中查找一个不存在的关键字
    OSError 操作系统产生的异常(例如打开一个不存在的文件)
    EOFError 用户输入文件末尾标志 EOF(Ctrl+d)
    AssertionError 断言语句(assert)失败

十二、文件 I/O

Python 中的基本输入机制是基于行的:从文本文件向程序读入数据时,一次会到达一个数据行。

使用 for 循环迭代一个文件:for each_line in file

  1. 文件打开方式:

    打开方式 解释
    ‘r’ 以只读方式打开文件(默认)
    ‘w’ 以写入的方式打开文件,会覆盖已存在的文件
    ‘x’ 如果文件已经存在,使用此模式打开将引发异常
    ‘a’ 以写入模式打开,如果文件存在,则在末尾追加写入
    ‘b’ 以二进制模式打开文件
    ‘t’ 以文本模式打开(默认)
    ‘+’ 可读写模式(可添加到其他模式中使用)
    ‘U’ 通用换行符支持
  2. 文件对象内置方法:

    方法 功能
    f.close() 关闭文件
    f.read([size=-1]) 从文件读取 size 个字符,当未给定 size 或给定负值的时候,读取剩余的所有字符,然后作为字符串返回
    f.readline([size=-1]) 从文件中读取并返回一行(包括行结束符),如果有 size 有定义则返回 size 个字符
    f.readlines() 返回一个列表,列表中每一项是文件的一行
    f.write(str) 将字符串 str 写入文件
    f.writelines(seq) 向文件写入字符串序列 seqseq 应该是一个返回字符串的可迭代对象
    f.seek(offset, from) 在文件中移动文件指针,从 from(0 代表文件起始位置,1 代表当前位置,2 代表文件末尾)偏移 offset 个字节
    f.tell() 返回文件指针的位置
    f.truncate([size=file.tell()]) 截取文件到 size 个字节,默认是截取到文件指针当前位置

使用上下文管理器自动关闭文件:

1
2
with open('文件名', '打开方式') as 变量名:
with语句块

十三、常用标准库及其包含的函数

13.1 random 模块

  • random.randint(0, 10):返回一个 0~10 之间的随机数

  • random.choice(列表):随机返回列表中的一个值

  • random.random():随机返回一个 [0, 1) 之间的数

13.2 pickle(泡菜) 模块

几乎可以将任何对象(包括序列,字典,集合等)转化为二进制的字节流存入文件

  • pickle.dump(object, file):将对象转化为二进制存入文件(文件打开方式需要是 wb

  • pickle.load(file):将文件内容转化为列表返回(文件打开方式需要是 rb

13.3 json 模块

  • json.load(文件对象)

  • json.loads(json对象)

  • json.dump(文件对象)

  • json.dumps(包含数据的字典)

13.4 base64 模块

base64.b64encode(binary string):将二进制字符串 base64 编码

base64.b64decode(binary string):将二进制字符串 base64 解码

1
2
3
4
5
>>> import base64
>>> base64.b64encode(b'binary\x00string')
b'YmluYXJ5AHN0cmluZw=='
>>> base64.b64decode(b'YmluYXJ5AHN0cmluZw==')
b'binary\x00string'

13.5 datetime 模块

  1. 获取代表当前日期和时间的 datetime 对象

    1
    2
    3
    4
    from datetime import datetime

    now = datetime.now()
    print(now) # 2020-01-08 00:16:12.865886
  2. 从指定的日期和时间获取 datetime 对象

    1
    2
    3
    4
    from datetime import datetime

    dt = datetime(2020, 1, 7, 23, 59)
    print(dt) # 2020-01-07 23:59:00
  3. datetime 对象与 timestamp(时间戳)的相互转换

    在计算机中,时间实际上是用数字表示的。我们把 1970 年 1 月 1 日 00:00:00 UTC+00:00 时区的时刻称为 epoch time,记为0,当前时间就是相对于 epoch time 的秒数,称为 timestamp(1970 年以前的时间 timestamp 为负数)。

    timestamp 的值与时区毫无关系,因为 timestamp 一旦确定,其 UTC 时间就确定了,转换到任意时区的时间也是完全确定的,这就是为什么计算机存储的当前时间是以timestamp 表示的,因为全球各地的计算机在任意时刻的 timestamp 都是完全相同的。

    • datetime 对象转换为 timestamp:

      1
      2
      3
      4
      from datetime import datetime

      dt = datetime(2020, 1, 7, 23, 59)
      dt.timestamp() # 1578412740.0

      注意:Python 的 timestamp 是一个浮点数整数位表示秒数,小数位表示毫秒数。而 JavaScript、Java 等语言中,timestamp 是一个整数,表示毫秒数

    • timestamp 转换为 datetime 对象:

      1
      2
      3
      4
      5
      from datetime import datetime

      t = 1578412740.0
      dt = datetime.fromtimestamp(t)
      print(dt) # 2020-01-07 23:59:00
  4. datetime 对象与字符串的相互转换

    • 字符串转 datetime

      1
      2
      3
      4
      from datetime import datetime

      dt = datetime.strptime("2020-1-7 23:59", "%Y-%m-%d %H:%M:%S")
      print(dt) # 2020-01-07 23:59:00

      字符串%Y-%m-%d %H:%M:%S规定了日期和时间的格式。详细的说明可以参考Python文档

    • datetime 转字符串:

      1
      2
      3
      4
      5
      from datetime import datetime

      now = datetime.now()
      strdt = now.strftime("%a, %b %d %H:%M")
      print(strdt) # Wed, Jan 08 00:38
  5. datetime 加减

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    from datetime import datetime, timedelta	# 需要导入 timedelta 类

    now = datetime.now()
    print(now) # 2020-01-08 00:41:15.807337

    dt1 = now + timedelta(days=1)
    print(dt1) # 2020-01-09 00:41:15.807337

    dt2 = now - timedelta(hours=5, days=3)
    print(dt2) # 2020-01-04 19:41:15.807337

13.6 getpass 模块

该模块提供平台无关的在命令行下输入密码的方法

包含方法 getuser(), getpass(),包含异常 GetPassWarning

  • getuser():该函数会检查环境变量 LOGNAME, USER, LNAMEUSERNAME, 以返回一个非空字符串。如果这些变量的设置为空的话,会从支持密码的数据库中获取用户名,否则会触发一个找不到用户的异常

  • getpass([string]):会显示提示字符串,关闭键盘的屏幕回显,然后读取密码。提示字符串默认为 Password:

  • GetPassWarning:当调用 getpass 方法输入密码时,密码有回显,就会抛出这个异常(如 IDLE 下,getpass 会回显)

13.7 collections 模块

  • Counter() 函数:对序列元素计数,计数结果可以转化为字典或双值子序列

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    from collections import Counter

    breakfast = ['spam', 'spam', 'eggs', 'spam']
    breakfast_counter = Counter(breakfast)
    print(breakfast_counter) # output: Counter({'spam': 3, 'eggs': 1})
    print(breakfast_counter.most_common()) # output: [('spam', 3), ('eggs', 1)] 可以得到双值子序列

    lunch = ['eggs', 'eggs', 'bacon']
    lunch_counter = Counter(lunch)
    print(dict(lunch_counter)) # output: {'eggs': 2, 'bacon': 1}

    print(breakfast_counter + lunch_counter) # 计数器可以相加,得到合并后的计数结果
    print(breakfast_counter - lunch_counter) # 计数器也可以相减
  • OrderedDict():创建有序字典

    因为字典是无序的,所以有些时候可能得不到想要的结果,使用该函数可以创建有序字典

    1
    2
    3
    4
    from collections import OrderedDict

    # 与 dict() 相似,参数是一个双值子序列或一个字典或其他情况
    quotes = OrderedDict([('Moe', 'A wise guy, huh?'), ('Larry', 'Ow!'), ('Curly', 'Nyuk nyuk!')])
  • deque():创建双端队列

    双端队列:同时具有栈和队列的特征,两端都可以增删元素,但不能在中间增删。

    创建双端队列:

    1
    2
    from collections import deque
    dq = deque(序列)

13.8 pprint 模块

该模块用于美化输出。

1
2
3
4
5
6
7
8
9
10
from pprint import pprint

quotes = OrderedDict([('Moe', 'A wise guy, huh?'), ('Larry', 'Ow!'), ('Curly', 'Nyuk nyuk!')])

print(quotes) # output: OrderedDict([('Moe', 'A wise guy, huh?'), ('Larry', 'Ow!'), ('Curly', 'Nyuk nyuk!')])

pprint(quotes)
# {'Moe': 'A wise guy, huh?',
# 'Larry': 'Ow!',
# 'Curly': 'Nyuk nyuk!'}

13.9 sys 模块

  • sys.argv :一个列表,存储从命令行运行程序时传入的命令行参数

  • sys.path :是一个列表,存储 模块搜索路径

  • sys.setrecursionlimit(深度):设置最大递归深度,默认为 ** 层。

13.10 re 模块

在 Python 中,re 模块可以用来使用正则表达式匹配字符串。

主要利用方法 re.search(r'pattern', 'text')re.findall(r'pattern', 'text')re.match(r'pattern', 'text') 方法使用较少,因为它只能匹配 text 开头的字符串。

模式串使用原始字符串可以避免不必要的麻烦。

match 对象:re.search()re.match() 返回的对象。

对象的方法 含义
match.string 主串
match.re 匹配时使用的 pattern(正则表达式)
match.pos text 的开始位置
match.endpos text 的结束位置
match.group() 获得匹配后的字符串
match.start() 匹配的字符串在主串中的开始位置
match.end() 匹配的字符串在主串中的结束位置
match.span() 返回 (match.start(), match.end())

贪婪匹配和非贪婪匹配:

贪婪匹配:在满足条件的情况下,尽可能长的去匹配。如:text = "Pythonnnnn"pattern = r"Python+"。那么匹配到的结果是 Pythonnnnn,而不是 Python

非贪婪匹配:匹配最小的结果

在上例中,使用 ? 可达到非贪婪匹配的效果:pattern = r"Python+?"。注:本例貌似不大正确,日后再完善,但大概意思还是对的。

13.11 os 模块

函数 功能
os.getcwd() 返回当前工作目录
os.chdir(path) 改变工作目录
os.listdir(path='.') 列举指定目录中的文件名
os.mkdir(path) 创建单层目录,如该目录已存在抛出异常
os.makedirs(path) 递归创建多层目录,如该目录已存在抛出异常
os.remove(path) 删除文件
os.link('源文件路径', '链接文件路径') 创建硬链接
os.symlink('源文件路径', '链接文件路径') 创建符号链接
os.rmdir(path) 删除单层目录,如该目录非空则抛出异常
os.removedirs(path) 递归删除目录,从子目录到父目录逐层尝试删除,遇到目录非空则抛出异常
os.rename(old, new) 将文件重命名
os.system(command) 运行系统命令
os.walk(top) 遍历 top 路径以下所有的子目录,返回一个三元组:(路径, [包含目录], [包含文件])
常量 含义
os.curdir 指代当前目录
os.pardir 指代上一级目录
os.sep 输出操作系统特定的路径分隔符(win下为 \\,Linux下为 /
os.linesep 当前平台使用的行终止符(win下为 \r\n,Linux下为 \n
os.name 指代当前使用的操作系统(包括:’posix’, ‘nt’, ‘mac’, ‘os2’, ‘ce’, ‘java’)
os.environ 一个对象,包含所有系统环境变量和一些方法。使用 os.environ.get(环境变量名) 可以获取某个环境变量

13.12 os.path 模块

方法 功能
os.path.basename(path) 去掉目录路径,单独返回文件名
os.path.dirname(path) 去掉文件名,单独返回目录路径
os.path.abspath('文件名') 获取文件绝对路径
os.path.realpath('符号链接文件') 获取源文件的绝对路径
os.path.join(path1[, path2[, ...]]) path1, path2 各部分组合成一个路径名
os.path.split(path) 分割文件名与路径,返回 (f_path, f_name) 元组。如果完全使用目录,它也会将最后一个目录作为文件名分离,且不会判断文件或者目录是否存在
os.path.splitext(path) 分离文件名与扩展名,返回 (f_name, f_extension) 元组
os.path.getsize(file) 返回指定文件的尺寸,单位是字节
os.path.getatime(file) 返回指定文件最近的访问时间(浮点型秒数,可用 time 模块的 gmtime()localtime() 函数换算)
os.path.getctime(file) 返回指定文件的创建时间(浮点型秒数,可用 time 模块的 gmtime()localtime() 函数换算)
os.path.getmtime(file) 返回指定文件最新的修改时间(浮点型秒数,可用 time 模块的 gmtime()localtime() 函数换算)
os.path.exists(path) 判断指定路径(目录或文件)是否存在
os.path.isabs(path) 判断指定路径是否为绝对路径
os.path.isdir(path) 判断指定路径是否存在且是一个目录
os.path.isfile(path) 判断指定路径是否存在且是一个文件
os.path.islink(path) 判断指定路径是否存在且是一个符号链接
os.path.ismount(path) 判断指定路径是否存在且是一个挂载点
os.path.samefile(path1, paht2) 判断 path1path2 两个路径是否指向同一个文件

13.13 shutil 模块

shutil.copy('原路径', '目标路径'):拷贝文件

13.14 struct 模块

用 struct 模块可以实现字符串和二进制数据间的相互转换,常用来解析网络数据、C 语言结构体对应的数据。

常用方法

struct.pack(fmt, data1, data2, ...):用于将 Python 的值根据格式字符串转换为 bytes

struct.unpack(fmt, bytes):将 bytes 按照格式字符串给定的格式解析成一个元组。

格式字符串

字节顺序,大小和对齐方式:

字符 字节顺序 大小 对齐方式
@ 按原字节 按原字节 按原字节
= 按原字节 标准
< 小端 标准
> 大端 标准
! 网络(=大端) 标准

格式字符:

格式 C 类型 Python 类型 标准大小 注释
x 填充字节
c char 长度为 1 的字节串 1
b signed char 整数 1 (1), (2)
B unsigned char 整数 1 (2)
? _Bool bool 1 (1)
h short 整数 2 (2)
H unsigned short 整数 2 (2)
i int 整数 4 (2)
I unsigned int 整数 4 (2)
l long 整数 4 (2)
L unsigned long 整数 4 (2)
q long long 整数 8 (2)
Q unsigned long long 整数 8 (2)
n ssize_t 整数 (3)
N size_t 整数 (3)
e (6) float 2 (4)
f float float 4 (4)
d double float 8 (4)
s char[] 字节串
p char[] 字节串
P void * 整数 (5)

使用样例

样例一:将 int 转换为 bytes,然后再转换成两个 short

1
2
3
4
5
import struct

bin_num = struct.pack('>i', 65536) # 使用大端模式读入
print(bin_num) # b'\x00\x00\x01\x00'
print(struct.unpack('>hh', bin_num)) # (1, 0)

样例二:元组和 C 结构体相互转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
'''
假设结构体的结构如下:
struct header {
int buf1;
double buf2;
char buf3[11];
}
'''

import struct

buf1 = xxx
buf2 = xxx.xxx
buf3 = "xxxxxxxxxxx"

header = struct.pack('id11s', buf1, buf2, bytes(buf3, encoding='ascii')) # 构造结构体
buf1, buf2, buf3 = struct.unpack('id11s', header) # 解包结构体

十四、一些概念与技巧

  • Python 的多行注释使用长字符串实现。

  • 字符串拼接可以不写加号,缺点是可读性不好:

    1
    string = 'Hello ' 'world'
  • print()file 关键字参数:指定要将数据写入哪个文件。

  • Python 文件头部注释:

    1
    2
    #! /usr/local/bin/python3	指定 linux 系统应该使用哪个解释器来执行程序
    # -*- coding:utf-8 -*- 指定文件编码
  • python 产生一个随机字符串(常用来作为文件名):

    1
    2
    import uuid
    uuid.uuid4().hex
  • 双下划线包含的变量和方法都是 Python 的保留用法,因为开发者一般不会使用这种命名方法,所以可以避免命名冲突。

  • 显示 “Python 之禅”

    1
    import this

    Python 之禅中文版:

    《Python之禅》 Tim Peters

    优美胜于丑陋

    明了胜于隐晦

    简洁胜于复杂

    复杂胜于混乱

    扁平胜于嵌套

    宽松胜于紧凑

    可读性很重要

    即便是特例,也不可违背这些规则

    虽然现实往往不那么完美

    但是不应该放过任何异常

    除非你确定需要如此

    如果存在多种可能,不要猜测

    肯定有一种——通常也是唯一一种——最佳的解决方案

    虽然这并不容易,因为你不是Python之父

    动手比不动手要好

    但不假思索就动手还不如不做

    如果你的方案很难懂,那肯定不是一个好方案

    如果你的方案很好懂,那肯定是一个好方案

    命名空间非常有用,我们应当多加利用

  • 多线程:Python使用一种称为 全局解释器锁(Global Interpreter Lock,GIL)的技术来实现多线程。它要求 Python 只能在一个解释器进程中运行,即使有多个处理器。在这样的机制下,多线程的程序会串行运行,运行速度甚至比没有用线程慢得多。除非去除 GIL 限制(如果真的能去除),否则不要在 Python 编程中使用线程。

  • Python2 与 Python3 的区别(简单记录)

    • Python2 和 Python3 最明显的区别在于调用 print 的方式,最重要的区别在于处理 Unicode 字符的方式。
    • Python2 中一个 int 型包含 32 位,可以存储从 -2147483648 到 2147483647 的整数,一个 long 类型包含 64 位,可以存储从 -9223372036854775808 到 9223372036854775807 的整数;Python3 中不再有 long 类型,而 int 类型变得可以存储任意大小的整数,远远超过 64 位。

十五、其他文章索引

使用 Python 编写网络爬虫

Python + selenium 操作浏览器

Python 环境和依赖管理

十六、TO_LEARN

  • pillow:图片处理

  • email:对邮件的操作

  • lxml

  • openpyxl

  • flask web 开发

  • datetime:处理日期和时间(可以参考书籍:《Python 语言及其应用》)

  • 测试:Python Shell 和 IDLE 适合对小段代码测试,但对大型程序,需要用到测试框架,Python 提供了两个测试框架:unittest(基于流行的 xUnit)、doctest