记录一些经常用到的 Python 技巧和方法。

基础

变量是将名字和对象关联起来,变量名是对象的引用而不是对象本身。

  • 可变对象 的内存地址可以改变,也可以修改当前指向的对象的值,例如列表、字典和集合等;
  • 不可变对象 的内存地址永远不会改变,无法修改其值,例如整数、浮点数和字符串等。
1
2
3
4
a, b = [0] * 10, [{}] * 10          # 生成内容的地址均相同
print (id(a[0]) == id(a[1])) # True
print (type(a[0]), type(b[0])) # <class 'int'> <class 'dict'>
a = b; a = b[:] # 前者单纯地改变内存地址,后者能复制一份列表 b

数值类型和字符串类型的基础运算

1
2
3
4
5
6
7
8
9
10
11
12
0x(1), 0o(1), 0b(1)                # 十六进制,八进制和二进制的字面量
hex(), oct(), bin() # 转化成十六进制,八进制和二进制
int(a, base = 10) # 转化成 base 进制
a = 2+3j # 定义复数
print (a.real, a.imag) # 2 3
print (10 > 6 < 8) # True
### <20 左对齐,>20 右对齐,^20 居中。默认左对齐。
print("{0:.2f}{1:20s}".format(1.2, "123")) # 1.20123
print("{0:a<10d}".format(1)) # 1aaaaaaaaa
print(r"hello\nworld") # hello\nworld
'2' * 3 # 将字符串重复三遍
print (ord('我'), chr(25105)) # 25105 我

关于列表和迭代器

1
2
3
4
5
6
7
8
9
10
a = [True, False]
any(a)/all(a)/sum(a)/max(a)... # 对迭代器进行运算
for id, element in enumerate(a) # 带序号遍历列表
i, _, j = [1, 2, 3] # 解包,i=1, j=3
i, *j = [1, 2, 3] # 解包,i=1, j=[2,3]
a.insert(index, x) # 在 index 处插入元素 x
a.pop(index); del a[index] # 删除列表第 index 个元素
a.remove(val) # 删除值为 val 的元素(仅删除第一个)
a = b[:]; a = b.copy() # 复制一份列表 b
sorted(iterable[,key[, reverse]]) # 对列表/迭代器进行排序,返回新的结果

集合的元素和字典的键必须是不可变对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
a.issubset(b); a<b;                 # 判断 a 是否是 b 的子集
a.issuperset(b); a>b # 判断 a 是否是 b 的超集
a.union(b); a|b # 求 a 和 b 的并集
a.intersection(b); a&b # 求 a 和 b 的交集
a.difference(b); a-b # 求 a 和 b 的差集
a.symmertric_difference(b); a^b # 求 a 和 b 的对称差集
# 列表去重并保持原有的顺序
mailto = ['cc', 'bbbb', 'afa', 'sss', 'bbbb', 'cc', 'shafa']
addr_to = list(set(mailto))
addr_to.sort(key = mailto.index)

fac={1:"00", 2:"00"} # 初始化字典
fac=dict(math="01", python="02") # 键是字符串时初始化可省略引号
max(dict, key = vote.get) # 取字典里的元素最大值
del a[key]; a.pop(key) # 后者能额外返回被删除的值
a.keys(), a.values(), a.items() # 获得键列表、值列表、键值对列表
d = dict(zip(d.values(), d.keys())) # 字典的键值互换

函数的参数调用分成位置参数、关键字参数、带默认值参数、可变数量参数四种。

1
2
3
4
5
6
7
# 函数的默认参数值在函数对象被创建时计算一次
def init(arg, result=[]):
result.append(arg)
print (result)
init('a'), init('b') # ['a'], ['a', 'b']
def sum(*a): # 把不定数量的位置参数打包成元组
def sum(**d): # 把不定数量的关键字参数打包成字典

lambda 函数会 绑定全局变量地址而非其值。以下代码的解决方案 见此

1
2
3
4
funcs = []
for i in range(10):
funcs.append(lambda x: x + i)
# 最后 funcs 里的十个函数都变成了:f(x) = x +10

除了最常规的实例方法外,python 还有用 @staticmethod 修饰的静态方法和 @classmethod 修饰的类方法。实例方法依赖实例 self,类方法依赖类 cls 或实例 self,静态方法不依赖类和实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Example:
static_attributes = "a"
def object_method(self, name):
print (name)
@classmethod # 向类调用方式声明需要传入 cls,对象调用不受影响
def class_method(cls):
print (cls.n1)
@staticmethod # 向对象调用方式声明不需要传入 self,类调用不受影响
def static_method():
print(Example.attributes) # 静态方法调用静态变量
e = Example()
e.object_method("b")
Example.static_method() # 类名调用静态方法,可以不加 @static_method
e.static_method() # 对象名调用静态方法,必须加 @static_method
Example.class_method() # 类名调用静态方法,必须加 @class_method
e.class_method() # 对象名调用静态方法,可以不加 @class_method

类的继承用 class B(A),如果想定义抽象类则需引入 abc 库。

1
2
3
4
5
6
from abc import ABC, abstractmethod

class MyAbstractClass(ABC):
@abstractmethod
def my_abstract_method(self):
pass

Python 的类不存在严格意义上的私有成员,保护成员和私有成员常用 ___ 作为前缀。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Example:
__n1 = "a" # 私有,需通过 _Example__n1 访问
def __init__(self):
self.__2 = "b" # 类的私有属性,外部不能直接访问,需通过 _Example__n2 访问
def __n3(self): # 类的私有方法,外部不能直接访问,需通过 _Example__n3 访问
print (self.__n2)
def _n4(self): # 类的保护成员,外部可以直接访问,不受 from module import * 影响
pass
@property # 能以属性的方式查询
def n2(self):
return self.__n2
@n2.setter # 能以属性的方式修改
def n2(self, n2):
self.__n2 = n2

print (e.__n2) # AttributeError: 'Example' object has no attribute '__n2'
print (e._Example__n2) # b

其中,__xxx__ 是系统定义的特殊的类方法。

类或函数的方法 效果
dir() 类或对象的所有属性和方法(列表形式)
__dict__ 类或对象中的所有属性和值(字典形式)
__doc__ 描述信息,即定义开头长注释里的字符串;可用于模块、函数、类。
__new____init__ 前者用于类对象的创建(静态方法),后者用于对象的初始化(实例方法)。前者必须要给出返回值(对象),作为后者的第一个参数。
__del__ 类的析构函数
__module____class__ 前者表示对象所在模块,后者表示对象所属的类
__str__ 类需要转为字符串时(比如打印该对象)触发
__call__ 当对象后面接 () 后会触发
__getitem__ 当对象后面接 [] 后会触发
__setitem____delitem__ 连同上面的 __getitem__,使类像字典一样使用
__iter____next__ 使该类变得可迭代,可以用 for ... in ... 遍历

标准库

argparse 参数解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import argparse
parser = argparse.ArgumentParser(description="...")
# 增加一个必须的位置变量,且不允许简写
parser.add_argument('param1', type=str, required=True, allow_abbrev=False)
# 增加一个可选参数 --verbose,若用户带上则 parser.verbosity 为真
parser.add_argument("-v", "--verbosity", help="help_doc", action="store_true")
# 只能在 choices 里选择值,否则会报错
parser.add_argument("-v", "--verbosity", type=int, choices=[0, 1, 2], default=0)
# 计数命令行里 --verbosity/--v 的个数
parser,add_argument("-v", "--verbosity", action="count", default=0)
# 不允许 --verbose 和 --quiet 同时存在
group = parser.add_mutually_exclusive_group()
group.add_argument("-v", "--verbose", action="store_true")
group.add_argument("-q", "--quiet", action="store_true")
args = parser.parse_args()

collections 拓展数据结构

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
'''
namedtuple 具名数组,支持用属性名去访问元组
'''
import collections.namedtuple

User = namedtuple('User', ['name', 'age', 'id'])
User = namedtuple('User', 'name age id')

user = User('tester', '22', '001')
user = User._make(['Runoob', 'male', 12])
user = user._replace(age = 22)

print (user) # User(name='user1', sex='male', age=21)
print (user._asdict) # 将其转化为字典
print (user._fields) # 获取所有字段名

'''
Counter 是 dict 的子类,用于计数可哈希对象
'''
from collections import Counter
# 转换成 dict 再统计
occ = dict(Counter(lst))
num = max(occ, key = occ.get)
# 直接用 Counter 的方法
Counter(lst).most_common(k = top_k) # 返回出现次数前 top_k 的数字+次数的元组

functools 高阶函数

1
2
3
4
5
6
7
8
9
10
11
12
13
import functools

def multiply(x, y): return x * y
double = functools.partial(multiply, y=2)
print (double(1))

from functools import cmp_to_key

def compare(ele1, ele2):
return ele2 - ele1
a = [2, 3, 1]
# 把基于两个数的 cmp 模式转化成基于单个数的 key 模式
print(sorted(a, key=functools.cmp_to_key(compare)))

itertools 实用的枚举方法

itertools 的所有方法都实现了生成器函数,高效而节省内存。

1
2
3
4
from itertools import permutations, combinations, product
permutations(list, r = len(list)) # list 里长度为 r 的所有排列
combinations(list, r) # list 里长度为 r 的所有组合
product(list1, list2, ...) # 所有 list 的笛卡尔积

json & pickle 序列化和反序列化

json 兼容标准的 JavaScript Object Notation 协议。

1
2
3
4
json.load(open('demo.json','r'))
json.loads('{'a':'1111','b':'2222'}')
json.dump(dict_or_list, open('demo.json', 'w'), indent = 2)
a_str = json.dumps(a_dict)

pickle 是 Python 专有基于二进制的序列化和反序列化。对不信任的数据进行 unpickling 是不安全的。

1
2
pickle.load(open('a.txt', 'rb'))
pickle.dump(a, open('a.txt', 'wb'))

random 随机

1
2
3
4
5
6
7
8
9
from random import *

random.seed(seed) # 设置随机种子(不设置每次会变)
random() # 返回 [0,1) 之间的浮点数。
uniform(a, b) # 返回 [a,b] 之间的浮点数。
randint(a, b) # 返回 [a,b] 之间的整数。
choice(seq) # 返回 seq 里随机的一个数。
shuffle(seq) # 打乱列表 seq。
sample(seq, k) # 从 seq 里随机获得 k 个数。

re 正则表达式

1
2
3
4
5
6
import re
re.compile(pattern[, flags]) # 编译一个正则表达式;调用以下函数时可以减少 pattern 这个参数
re.search(pattern, string) # 扫描整个字符串并返回第一个成功的
re.match (pattern, string) # 从字符串起始位置开始匹配
re.findall(pattern, string) # 扫描整个字符串并返回所有匹配成功的子串的 list
re.sub(pattern, repl, string) # 把 string 里所有极大 pattern 的改成 repl

第三方库

pyparsing 定制解析器

tesserocr & pytesseract 图片文本检测

对给定图片进行文本检测,并返回识别后的字符串。

注意不太适合很小的图(比如只包括一个字符),进行适当的缩放和重复会有更好的识别效果。

Sympy 数学表达式运算

比较详细的中文介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
x = sympy.Symbol('x')                   # 定义一个量 x
fx = 5 * x + 4 # 可以正常放入表达式运算
fx.free_symbols # 返回 fx 里的 Symbol 集合
y1 = fx.evalf(subs={x:6}) # 计算表达式的数值结果
y = sympy.pi * sympy.Symbol('y') # 注意 sin, pi 等要用 sympy 库里的
dx = sympy.diff(fx, x) # fx 关于 x 求导
sympy.integrate(fx, (x,0,1)) # fx 关于 x 定积分(只传一个 x 就是不定积分)
sympy.solve([fx, 2*y-1], (x, y), dict = True)
'''
解方程;方程列表,未知数列表(可不写)
dict = True:返回的一个 list 记录多解信息,list 里的每个元素是一个 dict 解集
manual = True:手动解,加上会比较稳定、比较快,但是 = False 更适合求联立的方程
rational = None/False/True:None 表示全用浮点数解,False/True 表示里面用分数解,返回值是浮点数/分数。
'''

Jupyter Notebook

即写即用,一直缓存结果,适合 python 开发。更推荐结合 VSCode。

在 notebook 里展示图片:

  • cv2.imshow("WindowsName", a) 无法正常显示图片,需要再后面加 cv2.waitKey(delay),如 delay=0 则跳出一张图片(鼠标关闭后才会继续运行)。
  • plt.imshow(a) 可以画出 a,但是会在代码运行完后再画。
  • plt.imshow(a); plt.show() 后可以立即输出图片(也可以连续画出多张图片)。

Numpy 专题

numpy 数组的几种初始化方法

1
2
3
4
5
6
7
8
9
a = np.array([(1,2,3), (4,5,6)])
# 元组也会变成一个维度,即 a = [[1, 2, 3], [4, 5, 6]]
a = np.zeros((3,4), order = 'F') # 列优先
a = np.full((3,5), 3.14, order = 'C') # 行优先
a = np.ones((2,3,4), dtype = np.int16)
a = np.identity(4) # 生成大小为 4 的单位矩阵
a = p.arange(0, 3, 0.1) # 和 range 用法类似
lst = a.tolist() # 转换成 list
b = a.copy() # deep_copy

numpy 数组索引可以用 :, 分隔,... 表示这一维度不要求。索引还可以是数组下标、布尔和表达式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
a = np.array([[1,2,3],[3,4,5],[4,5,6]])  
print (a[1,...]) # [3, 4, 5]
print (a[...,1:]) # [[2, 3], [4, 5], [5, 6]]

x = np.array([[1, 2], [3, 4], [5, 6]])
y = x[[0, 2]] # [[1, 2], [5, 6]]
y = x[[0, 1, 2], [0, 1, 0]] # [1, 4, 5]

rows = np.array([[0,0], [3,3]])
cols = np.array([[0,2], [0,2]])
y = x[rows, cols] # [[0, 2], [9, 11]]

a = np.array([[1,2,3], [4,5,6], [7,8,9]])
b = a[1:3, [1,2]] # [[5, 6] [8, 9]]

arr = np.array([[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]])
names = np.array(['Ben','Tom','Ben'])

arr[names == 'Ben'] # [[0, 1, 2, 3], [8, 9, 10, 11]]
arr[names == 'Ben', 3] # [3, 11]
arr[names != 'Ben', 1:4] # [5, 6, 7]

arr[arr < 3] # [0, 1, 2]
arr[arr > 3 and arr < 5] = 0

numpy 的数组操作

1
2
3
4
5
6
np.concatenate((a, b,...), axis = 0)    # 按第 0 维连接(其他维必须一样)  
np.squeeze(a, axis = [0]) / a.squeeze() # 留空表示全压缩
np.resize(a, (2, 3)) # 总数不同会用重复填充原数组;参数为 -1 则展平
np.reshape(a, (2, 3)) # 总数不同会报错;参数为 -1 则展平
a.flatten(order = 'F') # 将多维数组展平成一维
np.tile(a, (h, w)) # 把 a 按行方向填成原来的 h 倍,列方向 w 倍

numpy 的常用函数

1
2
3
4
5
np.random.choice(a, size, replace, p)   # 随机取 a 中 size 个, replace 表示是否放回
np.unique(b, return_index=True) # 将 b 数组去重,默认会给出排好序的结果
np.argsort(a, axis = []) # 按 axis 轴排序,返回相等大小的索引矩阵
sort(a, axis=-1, kind='quicksort') # 若 axis=None 则先展平再排序
np.min()/max()/mean()/sum() # 均可指定轴;均可用 ndarray 调用

numpy 的数学运算

1
2
3
4
5
6
7
8
np.sin()/cos()/tan()/log()/exp()        # 均可套 ndarray
np.multiply() / a * b # 对应元素相乘
np.power(x1,x2) # x2 可以是等列数的矩阵,表示乘方次数
np.matmul(a, b) / a @ b # 矩阵乘法
np.dot(a, b) # 若向量则是点积,若矩阵则是矩乘
np.linalg.det(a) # 矩阵行列式
np.linalg.inv(a) / a.I # 矩阵求逆
a.transpose() / a.T # 矩阵的转置

numpy 的 IO 操作

1
2
3
4
5
ny.save(file, a, allow_pickle=True)     # 将 a 保存至二进制文件(后缀名为 npy)
np.savez("runoob.npz", *args, **kwds) # 保存多个数组,如果非字典形式会被自动标号
b = np.load(file) # 如果读入的是 savez 后的文件会得到 dict
np.savetxt(file, a, fmt="%d") # 保存至普通 txt 文件
np.loadtxt(file, delimiter=' ')