Python语言进阶
重要知识点
-
生成式(推导式)的用法
prices = { 'AAPL': 191.88, 'GOOG': 1186.96, 'IBM': 149.24, 'ORCL': 48.44, 'ACN': 166.89, 'FB': 208.09, 'SYMC': 21.29 } # 用股票价格大于100元的股票构造一个新的字典 prices2 = {key: value for key, value in prices.items() if value > 100} print(prices2)说明:生成式(推导式)可以用来生成列表、集合和字典。
-
嵌套的列表的坑
names = ['关羽', '张飞', '赵云', '马超', '黄忠'] courses = ['语文', '数学', '英语'] # 录入五个学生三门课程的成绩 # 错误 - 参考http://pythontutor.com/visualize.html#mode=edit # scores = [[None] * len(courses)] * len(names) scores = [[None] * len(courses) for _ in range(len(names))] for row, name in enumerate(names): for col, course in enumerate(courses): scores[row][col] = float(input(f'请输入{name}的{course}成绩: ')) print(scores)Python Tutor - VISUALIZE CODE AND GET LIVE HELP
-
heapq模块(堆排序)""" 从列表中找出最大的或最小的N个元素 堆结构(大根堆/小根堆) """ import heapq list1 = [34, 25, 12, 99, 87, 63, 58, 78, 88, 92] list2 = [ {'name': 'IBM', 'shares': 100, 'price': 91.1}, {'name': 'AAPL', 'shares': 50, 'price': 543.22}, {'name': 'FB', 'shares': 200, 'price': 21.09}, {'name': 'HPQ', 'shares': 35, 'price': 31.75}, {'name': 'YHOO', 'shares': 45, 'price': 16.35}, {'name': 'ACME', 'shares': 75, 'price': 115.65} ] print(heapq.nlargest(3, list1)) print(heapq.nsmallest(3, list1)) print(heapq.nlargest(2, list2, key=lambda x: x['price'])) print(heapq.nlargest(2, list2, key=lambda x: x['shares'])) -
itertools模块""" 迭代工具模块 """ import itertools # 产生ABCD的全排列 itertools.permutations('ABCD') # 产生ABCDE的五选三组合 itertools.combinations('ABCDE', 3) # 产生ABCD和123的笛卡尔积 itertools.product('ABCD', '123') # 产生ABC的无限循环序列 itertools.cycle(('A', 'B', 'C')) -
collections模块常用的工具类:
namedtuple:命令元组,它是一个类工厂,接受类型的名称和属性列表来创建一个类。deque:双端队列,是列表的替代实现。Python中的列表底层是基于数组来实现的,而deque底层是双向链表,因此当你需要在头尾添加和删除元素时,deque会表现出更好的性能,渐近时间复杂度为$O(1)$。Counter:dict的子类,键是元素,值是元素的计数,它的most_common()方法可以帮助我们获取出现频率最高的元素。Counter和dict的继承关系我认为是值得商榷的,按照CARP原则,Counter跟dict的关系应该设计为关联关系更为合理。OrderedDict:dict的子类,它记录了键值对插入的顺序,看起来既有字典的行为,也有链表的行为。defaultdict:类似于字典类型,但是可以通过默认的工厂函数来获得键对应的默认值,相比字典中的setdefault()方法,这种做法更加高效。
""" 找出序列中出现次数最多的元素 """ from collections import Counter words = [ 'look', 'into', 'my', 'eyes', 'look', 'into', 'my', 'eyes', 'the', 'eyes', 'the', 'eyes', 'the', 'eyes', 'not', 'around', 'the', 'eyes', "don't", 'look', 'around', 'the', 'eyes', 'look', 'into', 'my', 'eyes', "you're", 'under' ] counter = Counter(words) print(counter.most_common(3))
数据结构和算法
-
算法:解决问题的方法和步骤
-
评价算法的好坏:渐近时间复杂度和渐近空间复杂度。
-
渐近时间复杂度的大O标记:
- 常量时间复杂度 - 布隆过滤器 / 哈希存储
- 对数时间复杂度 - 折半查找(二分查找)
- 线性时间复杂度 - 顺序查找 / 计数排序
- 对数线性时间复杂度 - 高级排序算法(归并排序、快速排序)
- 平方时间复杂度 - 简单排序算法(选择排序、插入排序、冒泡排序)
- 立方时间复杂度 - Floyd算法 / 矩阵乘法运算
- 几何级数时间复杂度 - 汉诺塔
- 阶乘时间复杂度 - 旅行经销商问题 - NPC


-
排序算法(选择、冒泡和归并)和查找算法(顺序和折半)
def select_sort(items, comp=lambda x, y: x < y): """简单选择排序""" items = items[:] for i in range(len(items) - 1): min_index = i for j in range(i + 1, len(items)): if comp(items[j], items[min_index]): min_index = j items[i], items[min_index] = items[min_index], items[i] return itemsdef bubble_sort(items, comp=lambda x, y: x > y): """冒泡排序""" items = items[:] for i in range(len(items) - 1): swapped = False for j in range(len(items) - 1 - i): if comp(items[j], items[j + 1]): items[j], items[j + 1] = items[j + 1], items[j] swapped = True if not swapped: break return itemsdef bubble_sort(items, comp=lambda x, y: x > y): """搅拌排序(冒泡排序升级版)""" items = items[:] for i in range(len(items) - 1): swapped = False for j in range(len(items) - 1 - i): if comp(items[j], items[j + 1]): items[j], items[j + 1] = items[j + 1], items[j] swapped = True if swapped: swapped = False for j in range(len(items) - 2 - i, i, -1): if comp(items[j - 1], items[j]): items[j], items[j - 1] = items[j - 1], items[j] swapped = True if not swapped: break return itemsdef merge(items1, items2, comp=lambda x, y: x < y): """合并(将两个有序的列表合并成一个有序的列表)""" items = [] index1, index2 = 0, 0 while index1 < len(items1) and index2 < len(items2): if comp(items1[index1], items2[index2]): items.append(items1[index1]) index1 += 1 else: items.append(items2[index2]) index2 += 1 items += items1[index1:] items += items2[index2:] return items def merge_sort(items, comp=lambda x, y: x < y): return _merge_sort(list(items), comp) def _merge_sort(items, comp): """归并排序""" if len(items) < 2: return items mid = len(items) // 2 left = _merge_sort(items[:mid], comp) right = _merge_sort(items[mid:], comp) return merge(left, right, comp)
函数的使用方式
-
将函数视为“一等公民”
- 函数可以赋值给变量
- 函数可以作为函数的参数
- 函数可以作为函数的返回值
-
高阶函数的用法(
filter、map以及它们的替代品)items1 = list(map(lambda x: x ** 2, filter(lambda x: x % 2, range(1, 10)))) items2 = [x ** 2 for x in range(1, 10) if x % 2] -
位置参数、可变参数、关键字参数、命名关键字参数
-
参数的元信息(代码可读性问题)
-
匿名函数和内联函数的用法(
lambda函数) -
闭包和作用域问题
-
Python搜索变量的LEGB顺序(Local >>> Embedded >>> Global >>> Built-in)
-
global和nonlocal关键字的作用global:声明或定义全局变量(要么直接使用现有的全局作用域的变量,要么定义一个变量放到全局作用域)。nonlocal:声明使用嵌套作用域的变量(嵌套作用域必须存在该变量,否则报错)。
-
-
装饰器函数(使用装饰器和取消装饰器)
例子:输出函数执行时间的装饰器。
def record_time(func): """自定义装饰函数的装饰器""" @wraps(func) def wrapper(*args, **kwargs): start = time() result = func(*args, **kwargs) print(f'{func.__name__}: {time() - start}秒') return result return wrapper如果装饰器不希望跟
print函数耦合,可以编写可以参数化的装饰器。from functools import wraps from time import time def record(output): """可以参数化的装饰器""" def decorate(func): @wraps(func) def wrapper(*args, **kwargs): start = time() result = func(*args, **kwargs) output(func.__name__, time() - start) return result return wrapper return decoratefrom functools import wraps from time import time class Record(): """通过定义类的方式定义装饰器""" def __init__(self, output): self.output = output def __call__(self, func): @wraps(func) def wrapper(*args, **kwargs): start = time() result = func(*args, **kwargs) self.output(func.__name__, time() - start) return result return wrapper说明:由于对带装饰功能的函数添加了@wraps装饰器,可以通过
func.__wrapped__方式获得被装饰之前的函数或类来取消装饰器的作用。例子:用装饰器来实现单例模式。
from functools import wraps def singleton(cls): """装饰类的装饰器""" instances = {} @wraps(cls) def wrapper(*args, **kwargs): if cls not in instances: instances[cls] = cls(*args, **kwargs) return instances[cls] return wrapper @singleton class President: """总统(单例类)""" pass提示:上面的代码中用到了闭包(closure),不知道你是否已经意识到了。还没有一个小问题就是,上面的代码并没有实现线程安全的单例,如果要实现线程安全的单例应该怎么做呢?
线程安全的单例装饰器。
面向对象相关知识
-
三大支柱:封装、继承、多态
例子:工资结算系统。
""" 月薪结算系统 - 部门经理每月15000 程序员每小时200 销售员1800底薪加销售额5%提成 """ from abc import ABCMeta, abstractmethod class Employee(metaclass=ABCMeta): """员工(抽象类)""" def __init__(self, name): self.name = name @abstractmethod def get_salary(self): """结算月薪(抽象方法)""" pass class Manager(Employee): """部门经理""" def get_salary(self): return 15000.0 class Programmer(Employee): """程序员""" def __init__(self, name, working_hour=0): self.working_hour = working_hour super().__init__(name) def get_salary(self): return 200.0 * self.working_hour class Salesman(Employee): """销售员""" def __init__(self, name, sales=0.0): self.sales = sales super().__init__(name) def get_salary(self): return 1800.0 + self.sales * 0.05 class EmployeeFactory: """创建员工的工厂(工厂模式 - 通过工厂实现对象使用者和对象之间的解耦合)""" @staticmethod def create(emp_type, *args, **kwargs): """创建员工""" all_emp_types = {'M': Manager, 'P': Programmer, 'S': Salesman} cls = all_emp_types[emp_type.upper()] return cls(*args, **kwargs) if cls else None def main(): """主函数""" emps = [ EmployeeFactory.create('M', '曹操'), EmployeeFactory.create('P', '荀彧', 120), EmployeeFactory.create('P', '郭嘉', 85), EmployeeFactory.create('S', '典韦', 123000), ] for emp in emps: print(f'{emp.name}: {emp.get_salary():.2f}元') if __name__ == '__main__': main() -
类与类之间的关系
- is-a关系:继承
- has-a关系:关联 / 聚合 / 合成
- use-a关系:依赖
例子:扑克游戏。
""" 经验:符号常量总是优于字面常量,枚举类型是定义符号常量的最佳选择 """ from enum import Enum, unique import random @unique class Suite(Enum): """花色""" SPADE, HEART, CLUB, DIAMOND = range(4) def __lt__(self, other): return self.value < other.value class Card: """牌""" def __init__(self, suite, face): """初始化方法""" self.suite = suite self.face = face def show(self): """显示牌面""" suites = ['♠︎', '♥︎', '♣︎', '♦︎'] faces = ['', 'A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'] return f'{suites[self.suite.value]}{faces[self.face]}' def __repr__(self): return self.show() class Poker: """扑克""" def __init__(self): self.index = 0 self.cards = [Card(suite, face) for suite in Suite for face in range(1, 14)] def shuffle(self): """洗牌(随机乱序)""" random.shuffle(self.cards) self.index = 0 def deal(self): """发牌""" card = self.cards[self.index] self.index += 1 return card @property def has_more(self): return self.index < len(self.cards) class Player: """玩家""" def __init__(self, name): self.name = name self.cards = [] def get_one(self, card): """摸一张牌""" self.cards.append(card) def sort(self, comp=lambda card: (card.suite, card.face)): """整理手上的牌""" self.cards.sort(key=comp) def main(): """主函数""" poker = Poker() poker.shuffle() players = [Player('东邪'), Player('西毒'), Player('南帝'), Player('北丐')] while poker.has_more: for player in players: player.get_one(poker.deal()) for player in players: player.sort() print(player.name, end=': ') print(player.cards) if __name__ == '__main__': main()
迭代器和生成器
-
迭代器是实现了迭代器协议的对象。
- Python中没有像
protocol或interface这样的定义协议的关键字。 - Python中用魔术方法表示协议。
__iter__和__next__魔术方法就是迭代器协议。
class Fib(object): """迭代器""" def __init__(self, num): self.num = num self.a, self.b = 0, 1 self.idx = 0 def __iter__(self): return self def __next__(self): if self.idx < self.num: self.a, self.b = self.b, self.a + self.b self.idx += 1 return self.a raise StopIteration() - Python中没有像
-
生成器是语法简化版的迭代器。
def fib(num): """生成器""" a, b = 0, 1 for _ in range(num): a, b = b, a + b yield a -
生成器进化为协程。
生成器对象可以使用
send()方法发送数据,发送的数据会成为生成器函数中通过yield表达式获得的值。这样,生成器就可以作为协程使用,协程简单的说就是可以相互协作的子程序。def calc_avg(): """流式计算平均值""" total, counter = 0, 0 avg_value = None while True: value = yield avg_value total, counter = total + value, counter + 1 avg_value = total / counter gen = calc_avg() next(gen) print(gen.send(10)) print(gen.send(20)) print(gen.send(30))
并发编程
Python中实现并发编程的三种方案:多线程、多进程和异步I/O。并发编程的好处在于可以提升程序的执行效率以及改善用户体验;坏处在于并发的程序不容易开发和调试,同时对其他程序来说它并不友好。
-
多线程:Python中提供了
Thread类并辅以Lock、Condition、Event、Semaphore和Barrier。Python中有GIL来防止多个线程同时执行本地字节码,这个锁对于CPython是必须的,因为CPython的内存管理并不是线程安全的,因为GIL的存在多线程并不能发挥CPU的多核特性。""" 面试题:进程和线程的区别和联系? 进程 - 操作系统分配内存的基本单位 - 一个进程可以包含一个或多个线程 线程 - 操作系统分配CPU的基本单位 并发编程(concurrent programming) 1. 提升执行性能 - 让程序中没有因果关系的部分可以并发的执行 2. 改善用户体验 - 让耗时间的操作不会造成程序的假死 """ import glob import os import threading from PIL import Image PREFIX = 'thumbnails' def generate_thumbnail(infile, size, format='PNG'): """生成指定图片文件的缩略图""" file, ext = os.path.splitext(infile) file = file[file.rfind('/') + 1:] outfile = f'{PREFIX}/{file}_{size[0]}_{size[1]}.{ext}' img = Image.open(infile) img.thumbnail(size, Image.ANTIALIAS) img.save(outfile, format) def main(): """主函数""" if not os.path.exists(PREFIX): os.mkdir(PREFIX) for infile in glob.glob('images/*.png'): for size in (32, 64, 128): # 创建并启动线程 threading.Thread( target=generate_thumbnail, args=(infile, (size, size)) ).start() if __name__ == '__main__': main()多个线程竞争资源的情况。
""" 多线程程序如果没有竞争资源处理起来通常也比较简单 当多个线程竞争临界资源的时候如果缺乏必要的保护措施就会导致数据错乱 说明:临界资源就是被多个线程竞争的资源 """ import time import threading from concurrent.futures import ThreadPoolExecutor class Account(object): """银行账户""" def __init__(self): self.balance = 0.0 self.lock = threading.Lock() def deposit(self, money): # 通过锁保护临界资源 with self.lock: new_balance = self.balance + money time.sleep(0.001) self.balance = new_balance def main(): """主函数""" account = Account() # 创建线程池 pool = ThreadPoolExecutor(max_workers=10) futures = [] for _ in range(100): future = pool.submit(account.deposit, 1) futures.append(future) # 关闭线程池 pool.shutdown() for future in futures: future.result() print(account.balance) if __name__ == '__main__': main()修改上面的程序,启动5个线程向账户中存钱,5个线程从账户中取钱,取钱时如果余额不足就暂停线程进行等待。为了达到上述目标,需要对存钱和取钱的线程进行调度,在余额不足时取钱的线程暂停并释放锁,而存钱的线程将钱存入后要通知取钱的线程,使其从暂停状态被唤醒。可以使用
threading模块的Condition来实现线程调度,该对象也是基于锁来创建的,代码如下所示:""" 多个线程竞争一个资源 - 保护临界资源 - 锁(Lock/RLock) 多个线程竞争多个资源(线程数>资源数) - 信号量(Semaphore) 多个线程的调度 - 暂停线程执行/唤醒等待中的线程 - Condition """ from concurrent.futures import ThreadPoolExecutor from random import randint from time import sleep import threading class Account: """银行账户""" def __init__(self, balance=0): self.balance = balance lock = threading.RLock() self.condition = threading.Condition(lock) def withdraw(self, money): """取钱""" with self.condition: while money > self.balance: self.condition.wait() new_balance = self.balance - money sleep(0.001) self.balance = new_balance def deposit(self, money): """存钱""" with self.condition: new_balance = self.balance + money sleep(0.001) self.balance = new_balance self.condition.notify_all() def add_money(account): while True: money = randint(5, 10) account.deposit(money) print(threading.current_thread().name, ':', money, '====>', account.balance) sleep(0.5) def sub_money(account): while True: money = randint(10, 30) account.withdraw(money) print(threading.current_thread().name, ':', money, '<====', account.balance) sleep(1) def main(): account = Account() with ThreadPoolExecutor(max_workers=15) as pool: for _ in range(5): pool.submit(add_money, account) for _ in range(10): pool.submit(sub_money, account) if __name__ == '__main__': main()