Python 编程技巧

有效利用 Python 的数据结构和内置功能

Posted by Paradise on April 2, 2021

参考文章:https://mp.weixin.qq.com/s/G9TjR6c_oqKF3qujcGUhGA##

选用正确的内置功能

使用 Map,Reduce 函数

1
2
3
map(lambda x: x+1, [1,2,3,4,5])     # 返回生成器
from functools import reduce
reduce(lambda a1,a2: a1*a2, [1,2,3,4,5]) # 等于 5!

使用 enumerate 自动将列表生成带索引的元组列表

1
2
3
lis = [1, 2, 3, 4, 5]
for i, num in enumerate(lis, start=10):
    print(i, num)

使用 zip 将两个列表结合为元组列表

1
2
3
4
lis1 = [5,4,3,2,1]
lis2 = [1,2,3,4,5]
for i1, i2 in zip(lis1, lis2):
    print(i1 + i2)

使用列表推导式而不是 map 或 filter

1
2
3
4
5
6
7
number = [1, 2, 3, 4, 5]
# 使用 map 和 filter
list(map(lambda x: x**2, number))
list(filter(lambda x: bool(x % 2), number))    #返回奇数
# 使用递推式:
print([x**2 for x in number])
print([x for x in number if bool(x % 2)])

使用 sorted 对列表排序

1
2
sorted([5,3,2,4,1,6,8,7,9])
sorted(['cat', 'dog', 'pig', 'duck', 'women'])

其他:

  • 使用 breakpoint() 进行断点调试而不是 print()
  • 使用 f-strings 格式化字符串


充分利用数据结构

使用 set 储存唯一值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import random
all_words = 'You know what? I am a sexy motherfucker!'.split()
get_random_word = lambda : random.choice(all_words)
# 方法一:创建一个列表,使用条件语句将其转换为集合
words = []
for _ in range(1000):
    word = get_random_word()
    if word not in words:
        words.append(word)
print(words)
# 方法二:无需创建列表对象再唯一化,直接创建set对象
words = set()
for _ in range(1000):
    words.add(get_random_word())    #注意对于set对象是使用add方法
print(words)
# 在时间复杂度上,前者是线性的时间复杂度,后者接近恒定时间

使用生成器以节省内存

1
2
3
4
5
6
7
8
9
10
# 例子:计算1000w个正方形面积的总和(上到一亿就死机了,计算量指数上升的。。。)
import time
start = time.time()
sum([i**2 for i in range(int(1e7+1))])
print(f'使用列表推导共耗时{int(time.time()-start)}秒。。。')
# 当使用生成器,只需要把方括号改为圆括号:
start = time.time()
sum((i**2 for i in range(int(1e7+1))))
print(f'使用生成器共耗时{int(time.time()-start)}秒。。。')
# 原理:每次只有一个值占用内存,重复调用,用到才计算相应的值。

使用 .get() 和 .setdefault() 在字典中定义默认值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 例子:查看cowboy的名字
cowboy = {'age': 66, 'horse':'mustang', 'hat_size':'large'}
# 显式地检查key
if 'name' in cowboy:
    name = cowboy['name']
else:
    name = 'Unknown'
print(f'name: {name}')
# 使用.get()
name = cowboy.get('name', 'Unknown')    #第二个参数为返回默认值
# 显式地修改值
if 'name' not in cowboy:
    cowboy['name'] = 'Unknown'
# 使用.setdefault()
cowboy.setdefault('other_name', 'sucker')    #会返回默认值

使用 defaultdict 迭代创建多层嵌套字典

1
2
3
4
5
6
7
8
from collections import defaultdict
multi_level_dict = lambda : defaultdict(multi_level_dict)
# 引用了本函数,形成多层嵌套结构
d = multi_level_dict()
print(d)
d['a']['a']['y'] = 2
d['b']['c']['a'] = 5
d['x']['a'] = 6


利用 Python 标准库

利用 collections.defaultdict() 处理缺少的字典键值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 例子:学生成绩
student_grades = {}
grades = [('elliot', 91), ('neelam', 98), ('bianca', 81), ('elliot', 88)]
for name, grade in grades:
    if name not in student_grades:
        student_grades[name] = []
    student_grades[name].append(grade)
print(student_grades)
# 使用更简洁的方法
from collections import defaultdict
student_grades = defaultdict(list)    #这里是调用了list(),传入一个空列表
for name, grade in grades:
    student_grades[name].append(grade)
print(student_grades)

使用 collections.Counter 计算 Hashable 对象

1
2
3
4
5
6
# 例子:统计词频
from collections import Counter
words = 'mother duck said: qwark! qwark! qwark!'.split()
counts = Counter(words)
print(counts)
print(counts.most_common(2))

使用字符串常量访问公共字符串组

1
2
3
4
5
6
7
8
9
10
11
12
# 例子:判断字符串中是否全为大写
import string
def is_upper(word):
    for letter in word:
        if letter not in string.ascii_uppercase:
            return False
    return True
print(is_upper('ABCDe'))
print(is_upper('ABCDE'))
print(string.ascii_uppercase)
# 其他的还有:ascii_letters、ascii_lowercase、digits、hexdigits、octdigits、
#     punctuation、printable、whitespace

使用 Itertools 生成排列组合

1
2
3
4
5
6
7
8
9
import itertools
# 例子:朋友配对
friends = ['Monique', 'Ashish', 'Devon', 'Bernie']
# 排列
perm = itertools.permutations(friends, r=2)
print(list(perm))
# 组合
comb = itertools.combinations(friends, r=2)
print(list(comb))

使用 LRU 缓存提高迭代效率

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
import time
from functools import lru_cache

# 直接递归
def fib(number):
    '''一个简单的斐波那契函数'''
    if number == 0: return 0
    if number == 1: return 1
    # 递归调用
    return fib(number - 1) + fib(number-2)
start = time.time()
fib(30)
print(f"共耗时{time.time() - start}秒")

# 使用 lru_cache 装饰器
@lru_cache(maxsize = 512)
def fib_mem(number):
    '''一个简单的斐波那契函数'''
    if number == 0: return 0
    if number == 1: return 1
    # 递归调用
    return fib_mem(number - 1) + fib_mem(number-2)
start = time.time()
fib_memorization(40)
print(f"共耗时{time.time() - start}秒")


利用高级函数功能

创建闭包

闭包(closure)就是由其他函数动态生成并返回的函数。其关键性质是,被返回的函数可以访问创建它的函数的局部命名空间里面的变量。

闭包与标准 python 函数的区别在于,即使其创建者已经执行完毕,闭包还能继续访问其创建者的局部命名空间。在实际工作中,可以编写带有大量选项的非常一般化的函数,然后再组装出更简洁更专门化的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def make_closure(n):
    '''返回一个计算并打印 n 次幂的函数'''
    text = 'The result is: '
    def power(x):
        print(text, x**n)
    return power

power_2 = make_closure(2)
power_2(3)

power_sqrt = make_closure(1/2)
power_sqrt(1024)

# 可见 make_closure 函数调用结束后,仍然可以引用其内部的变量 text

使用扩展调用语法

对于一个函数的参数,调用时函数实际接受到的是由位置参数组成的元组或者由关键字参数组成的字典。函数在幕后完成上述转化打包。

1
2
3
4
5
6
7
8
9
10
11
def say_hello_then_call_function(f, *args, **kwargs):
    # 显示参数打包的结果
    print('arguments are: ', args)
    print('keyword arguments are: ', kwargs)
    print('Hello! Now I am gonna call %s' % f)
    # 调用函数并返回执行结果
    return f(*args, **kwargs)

func = lambda x, y, z: x + y * z
result = say_hello_then_call_function(func, 2, y=3, z=4)
print(result)

使用装饰器

1
2
3
4
5
6
7
8
9
10
11
12
# 示例:计时器装饰器
def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        func(*args, **kwargs)   # 执行输入的函数
        print(f"耗时:{time.time()-start} 秒")
    return wrapper

# 在定义函数时@一下返回装饰器的函数(相当于将函数输入定义了装饰器的函数里面运行)
@timer
def program_pause(t):
    time.sleep(t)