Python語(yǔ)言進(jìn)階
-
數(shù)據(jù)結(jié)構(gòu)和算法
算法:解決問(wèn)題的方法和步驟
評(píng)價(jià)算法的好壞:漸近時(shí)間復(fù)雜度和漸近空間復(fù)雜度锥涕。
-
漸近時(shí)間復(fù)雜度的大O標(biāo)記:
- <img src="http://latex.codecogs.com/gif.latex?O(c)" /> - 常量時(shí)間復(fù)雜度 - 布隆過(guò)濾器 / 哈希存儲(chǔ)
- <img src="http://latex.codecogs.com/gif.latex?O(log_2n)" /> - 對(duì)數(shù)時(shí)間復(fù)雜度 - 折半查找(二分查找)
- <img src="http://latex.codecogs.com/gif.latex?O(n)" /> - 線性時(shí)間復(fù)雜度 - 順序查找 / 桶排序
- <img src="http://latex.codecogs.com/gif.latex?O(n*log_2n)" /> - 對(duì)數(shù)線性時(shí)間復(fù)雜度 - 高級(jí)排序算法(歸并排序、快速排序)
- <img src="http://latex.codecogs.com/gif.latex?O(n^2)" /> - 平方時(shí)間復(fù)雜度 - 簡(jiǎn)單排序算法(選擇排序、插入排序刁笙、冒泡排序)
- <img src="http://latex.codecogs.com/gif.latex?O(n^3)" /> - 立方時(shí)間復(fù)雜度 - Floyd算法 / 矩陣乘法運(yùn)算
- <img src="http://latex.codecogs.com/gif.latex?O(2^n)" /> - 幾何級(jí)數(shù)時(shí)間復(fù)雜度 - 漢諾塔
- <img src="http://latex.codecogs.com/gif.latex?O(n!)" /> - 階乘時(shí)間復(fù)雜度 - 旅行經(jīng)銷(xiāo)商問(wèn)題 - NP
[圖片上傳失敗...(image-fa1bdb-1563764870433)]
[圖片上傳失敗...(image-1df412-1563764870433)]
-
排序算法(選擇、冒泡和歸并)和查找算法(順序和折半)
def select_sort(origin_items, comp=lambda x, y: x < y): """簡(jiǎn)單選擇排序""" items = origin_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 items
def bubble_sort(origin_items, comp=lambda x, y: x > y): """高質(zhì)量冒泡排序(攪拌排序)""" items = origin_items[:] for i in range(len(items) - 1): swapped = False for j in range(i, 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 items
def merge_sort(items, comp=lambda x, y: x <= y): """歸并排序(分治法)""" 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) def merge(items1, items2, comp): """合并(將兩個(gè)有序的列表合并成一個(gè)有序的列表)""" items = [] index, 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 seq_search(items, key): """順序查找""" for index, item in enumerate(items): if item == key: return index return -1
def bin_search(items, key): """折半查找""" start, end = 0, len(items) - 1 while start <= end: mid = (start + end) // 2 if key > items[mid]: start = mid + 1 elif key < items[mid]: end = mid - 1 else: return mid return -1
-
使用生成式(推導(dǎo)式)語(yǔ)法
prices = { 'AAPL': 191.88, 'GOOG': 1186.96, 'IBM': 149.24, 'ORCL': 48.44, 'ACN': 166.89, 'FB': 208.09, 'SYMC': 21.29 } # 用股票價(jià)格大于100元的股票構(gòu)造一個(gè)新的字典 prices2 = {key: value for key, value in prices.items() if value > 100} print(prices2)
說(shuō)明:生成式(推導(dǎo)式)可以用來(lái)生成列表疲吸、集合和字典摘悴。
-
嵌套的列表
names = ['關(guān)羽', '張飛', '趙云', '馬超', '黃忠'] courses = ['語(yǔ)文', '數(shù)學(xué)', '英語(yǔ)'] # 錄入五個(gè)學(xué)生三門(mén)課程的成績(jī) # 錯(cuò)誤 - 參考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'請(qǐng)輸入{name}的{course}成績(jī): ')) print(scores)
Python Tutor - VISUALIZE CODE AND GET LIVE HELP
-
heapq蹂喻、itertools等的用法
""" 從列表中找出最大的或最小的N個(gè)元素 堆結(jié)構(gòu)(大根堆/小根堆) """ 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']))
""" 迭代工具 - 排列 / 組合 / 笛卡爾積 """ import itertools itertools.permutations('ABCD') itertools.combinations('ABCDE', 3) itertools.product('ABCD', '123')
-
collections模塊下的工具類(lèi)
""" 找出序列中出現(xiàn)次數(shù)最多的元素 """ 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))
-
常用算法:
- 窮舉法 - 又稱為暴力破解法秦陋,對(duì)所有的可能性進(jìn)行驗(yàn)證驳概,直到找到正確答案顺又。
- 貪婪法 - 在對(duì)問(wèn)題求解時(shí)稚照,總是做出在當(dāng)前看來(lái)
- 最好的選擇流济,不追求最優(yōu)解绳瘟,快速找到滿意解。
- 分治法 - 把一個(gè)復(fù)雜的問(wèn)題分成兩個(gè)或更多的相同或相似的子問(wèn)題蘸泻,再把子問(wèn)題分成更小的子問(wèn)題悦施,直到可以直接求解的程度抡诞,最后將子問(wèn)題的解進(jìn)行合并得到原問(wèn)題的解鬼雀。
- 回溯法 - 回溯法又稱為試探法源哩,按選優(yōu)條件向前搜索励烦,當(dāng)搜索到某一步發(fā)現(xiàn)原先選擇并不優(yōu)或達(dá)不到目標(biāo)時(shí)漆魔,就退回一步重新選擇改抡。
- 動(dòng)態(tài)規(guī)劃 - 基本思想也是將待求解問(wèn)題分解成若干個(gè)子問(wèn)題阿纤,先求解并保存這些子問(wèn)題的解欠拾,避免產(chǎn)生大量的重復(fù)運(yùn)算资昧。
窮舉法例子:百錢(qián)百雞和五人分魚(yú)格带。
# 公雞5元一只 母雞3元一只 小雞1元三只 # 用100元買(mǎi)100只雞 問(wèn)公雞/母雞/小雞各多少只 for x in range(20): for y in range(33): z = 100 - x - y if 5 * x + 3 * y + z // 3 == 100 and z % 3 == 0: print(x, y, z) # A、B棺亭、C镶摘、D、E五人在某天夜里合伙捕魚(yú) 最后疲憊不堪各自睡覺(jué) # 第二天A第一個(gè)醒來(lái) 他將魚(yú)分為5份 扔掉多余的1條 拿走自己的一份 # B第二個(gè)醒來(lái) 也將魚(yú)分為5份 扔掉多余的1條 拿走自己的一份 # 然后C、D蒙袍、E依次醒來(lái)也按同樣的方式分魚(yú) 問(wèn)他們至少捕了多少條魚(yú) fish = 6 while True: total = fish enough = True for _ in range(5): if (total - 1) % 5 == 0: total = (total - 1) // 5 * 4 else: enough = False break if enough: print(fish) break fish += 5
貪婪法例子:假設(shè)小偷有一個(gè)背包,最多能裝20公斤贓物以现,他闖入一戶人家邑遏,發(fā)現(xiàn)如下表所示的物品。很顯然纪吮,他不能把所有物品都裝進(jìn)背包碾盟,所以必須確定拿走哪些物品晚胡,留下哪些物品估盘。
名稱 價(jià)格(美元) 重量(kg) 電腦 200 20 收音機(jī) 20 4 鐘 175 10 花瓶 50 2 書(shū) 10 1 油畫(huà) 90 9 """ 貪婪法:在對(duì)問(wèn)題求解時(shí),總是做出在當(dāng)前看來(lái)是最好的選擇箫踩,不追求最優(yōu)解,快速找到滿意解慨削。 輸入: 20 6 電腦 200 20 收音機(jī) 20 4 鐘 175 10 花瓶 50 2 書(shū) 10 1 油畫(huà) 90 9 """ class Thing(object): """物品""" def __init__(self, name, price, weight): self.name = name self.price = price self.weight = weight @property def value(self): """價(jià)格重量比""" return self.price / self.weight def input_thing(): """輸入物品信息""" name_str, price_str, weight_str = input().split() return name_str, int(price_str), int(weight_str) def main(): """主函數(shù)""" max_weight, num_of_things = map(int, input().split()) all_things = [] for _ in range(num_of_things): all_things.append(Thing(*input_thing())) all_things.sort(key=lambda x: x.value, reverse=True) total_weight = 0 total_price = 0 for thing in all_things: if total_weight + thing.weight <= max_weight: print(f'小偷拿走了{(lán)thing.name}') total_weight += thing.weight total_price += thing.price print(f'總價(jià)值: {total_price}美元') if __name__ == '__main__': main()
分治法例子:快速排序。
""" 快速排序 - 選擇樞軸對(duì)元素進(jìn)行劃分玫芦,左邊都比樞軸小右邊都比樞軸大 """ def quick_sort(origin_items, comp=lambda x, y: x <= y): items = origin_items[:] _quick_sort(items, 0, len(items) - 1, comp) return items def _quick_sort(items, start, end, comp): if start < end: pos = _partition(items, start, end, comp) _quick_sort(items, start, pos - 1, comp) _quick_sort(items, pos + 1, end, comp) def _partition(items, start, end, comp): pivot = items[end] i = start - 1 for j in range(start, end): if comp(items[j], pivot): i += 1 items[i], items[j] = items[j], items[i] items[i + 1], items[end] = items[end], items[i + 1] return i + 1
回溯法例子:騎士巡邏。
""" 遞歸回溯法:叫稱為試探法,按選優(yōu)條件向前搜索张遭,當(dāng)搜索到某一步菊卷,發(fā)現(xiàn)原先選擇并不優(yōu)或達(dá)不到目標(biāo)時(shí)歉甚,就退回一步重新選擇纸泄,比較經(jīng)典的問(wèn)題包括騎士巡邏、八皇后和迷宮尋路等衡便。 """ import sys import time SIZE = 5 total = 0 def print_board(board): for row in board: for col in row: print(str(col).center(4), end='') print() def patrol(board, row, col, step=1): if row >= 0 and row < SIZE and \ col >= 0 and col < SIZE and \ board[row][col] == 0: board[row][col] = step if step == SIZE * SIZE: global total total += 1 print(f'第{total}種走法: ') print_board(board) patrol(board, row - 2, col - 1, step + 1) patrol(board, row - 1, col - 2, step + 1) patrol(board, row + 1, col - 2, step + 1) patrol(board, row + 2, col - 1, step + 1) patrol(board, row + 2, col + 1, step + 1) patrol(board, row + 1, col + 2, step + 1) patrol(board, row - 1, col + 2, step + 1) patrol(board, row - 2, col + 1, step + 1) board[row][col] = 0 def main(): board = [[0] * SIZE for _ in range(SIZE)] patrol(board, SIZE - 1, SIZE - 1) if __name__ == '__main__': main()
動(dòng)態(tài)規(guī)劃例子1:斐波拉切數(shù)列。(不使用動(dòng)態(tài)規(guī)劃將會(huì)是幾何級(jí)數(shù)復(fù)雜度)
""" 動(dòng)態(tài)規(guī)劃 - 適用于有重疊子問(wèn)題和最優(yōu)子結(jié)構(gòu)性質(zhì)的問(wèn)題 使用動(dòng)態(tài)規(guī)劃方法所耗時(shí)間往往遠(yuǎn)少于樸素解法(用空間換取時(shí)間) """ def fib(num, temp={}): """用遞歸計(jì)算Fibonacci數(shù)""" if num in (1, 2): return 1 try: return temp[num] except KeyError: temp[num] = fib(num - 1) + fib(num - 2) return temp[num]
動(dòng)態(tài)規(guī)劃例子2:子列表元素之和的最大值。(使用動(dòng)態(tài)規(guī)劃可以避免二重循環(huán))
說(shuō)明:子列表指的是列表中索引(下標(biāo))連續(xù)的元素構(gòu)成的列表鹊碍;列表中的元素是int類(lèi)型年枕,可能包含正整數(shù)熏兄、0桥状、負(fù)整數(shù)辅斟;程序輸入列表中的元素查邢,輸出子列表元素求和的最大值扰藕,例如:
輸入:1 -2 3 5 -3 2
輸出:8
輸入:0 -2 3 5 -1 2
輸出:9
輸入:-9 -2 -3 -5 -3
輸出:-2
def main(): items = list(map(int, input().split())) size = len(items) overall, partial = {}, {} overall[size - 1] = partial[size - 1] = items[size - 1] for i in range(size - 2, -1, -1): partial[i] = max(items[i], partial[i + 1] + items[i]) overall[i] = max(partial[i], overall[i + 1]) print(overall[0]) if __name__ == '__main__': main()
-
函數(shù)的使用方式
-
將函數(shù)視為“一等公民”
- 函數(shù)可以賦值給變量
- 函數(shù)可以作為函數(shù)的參數(shù)
- 函數(shù)可以作為函數(shù)的返回值
-
高階函數(shù)的用法(
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]
位置參數(shù)徘熔、可變參數(shù)讶凉、關(guān)鍵字參數(shù)懂讯、命名關(guān)鍵字參數(shù)
參數(shù)的元信息(代碼可讀性問(wèn)題)
匿名函數(shù)和內(nèi)聯(lián)函數(shù)的用法(
lambda
函數(shù))-
閉包和作用域問(wèn)題
Python搜索變量的LEGB順序(Local --> Embedded --> Global --> Built-in)
-
global
和nonlocal
關(guān)鍵字的作用global
:聲明或定義全局變量(要么直接使用現(xiàn)有的全局作用域的變量,要么定義一個(gè)變量放到全局作用域)瘫里。nonlocal
:聲明使用嵌套作用域的變量(嵌套作用域必須存在該變量,否則報(bào)錯(cuò))劳殖。
-
裝飾器函數(shù)(使用裝飾器和取消裝飾器)
例子:輸出函數(shù)執(zhí)行時(shí)間的裝飾器。
def record_time(func): """自定義裝飾函數(shù)的裝飾器""" @wraps(func) def wrapper(*args, **kwargs): start = time() result = func(*args, **kwargs) print(f'{func.__name__}: {time() - start}秒') return result return wrapper
如果裝飾器不希望跟
print
函數(shù)耦合矛缨,可以編寫(xiě)帶參數(shù)的裝飾器誉简。from functools import wraps from time import time def record(output): """自定義帶參數(shù)的裝飾器""" 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 decorate
from functools import wraps from time import time class Record(): """自定義裝飾器類(lèi)(通過(guò)__call__魔術(shù)方法使得對(duì)象可以當(dāng)成函數(shù)調(diào)用)""" 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
說(shuō)明:由于對(duì)帶裝飾功能的函數(shù)添加了@wraps裝飾器筋量,可以通過(guò)
func.__wrapped__
方式獲得被裝飾之前的函數(shù)或類(lèi)來(lái)取消裝飾器的作用桨武。例子:用裝飾器來(lái)實(shí)現(xiàn)單例模式凉蜂。
from functools import wraps def singleton(cls): """裝飾類(lèi)的裝飾器""" 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(): """總統(tǒng)(單例類(lèi))""" pass
說(shuō)明:上面的代碼中用到了閉包(closure),不知道你是否已經(jīng)意識(shí)到了纫雁。還沒(méi)有一個(gè)小問(wèn)題就是,上面的代碼并沒(méi)有實(shí)現(xiàn)線程安全的單例忌愚,如果要實(shí)現(xiàn)線程安全的單例應(yīng)該怎么做呢?
from functools import wraps from threading import Lock def singleton(cls): """線程安全的單例裝飾器""" instances = {} locker = Lock() @wraps(cls) def wrapper(*args, **kwargs): if cls not in instances: with locker: if cls not in instances: instances[cls] = cls(*args, **kwargs) return instances[cls] return wrapper
-
-
面向?qū)ο笙嚓P(guān)知識(shí)
-
三大支柱:封裝衙耕、繼承橙喘、多態(tài)
例子:工資結(jié)算系統(tǒng)饰潜。
""" 月薪結(jié)算系統(tǒng) - 部門(mén)經(jīng)理每月15000 程序員每小時(shí)200 銷(xiāo)售員1800底薪加銷(xiāo)售額5%提成 """ from abc import ABCMeta, abstractmethod class Employee(metaclass=ABCMeta): """員工(抽象類(lèi))""" def __init__(self, name): self.name = name @abstractmethod def get_salary(self): """結(jié)算月薪(抽象方法)""" pass class Manager(Employee): """部門(mén)經(jīng)理""" 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): """銷(xiāo)售員""" 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(): """創(chuàng)建員工的工廠(工廠模式 - 通過(guò)工廠實(shí)現(xiàn)對(duì)象使用者和對(duì)象之間的解耦合)""" @staticmethod def create(emp_type, *args, **kwargs): """創(chuàng)建員工""" emp_type = emp_type.upper() emp = None if emp_type == 'M': emp = Manager(*args, **kwargs) elif emp_type == 'P': emp = Programmer(*args, **kwargs) elif emp_type == 'S': emp = Salesman(*args, **kwargs) return emp def main(): """主函數(shù)""" emps = [ EmployeeFactory.create('M', '曹操'), EmployeeFactory.create('P', '荀彧', 120), EmployeeFactory.create('P', '郭嘉', 85), EmployeeFactory.create('S', '典韋', 123000), ] for emp in emps: print('%s: %.2f元' % (emp.name, emp.get_salary())) if __name__ == '__main__': main()
-
類(lèi)與類(lèi)之間的關(guān)系
- is-a關(guān)系:繼承
- has-a關(guān)系:關(guān)聯(lián) / 聚合 / 合成
- use-a關(guān)系:依賴
例子:撲克游戲。
""" 經(jīng)驗(yàn):符號(hào)常量總是優(yōu)于字面常量半沽,枚舉類(lèi)型是定義符號(hào)常量的最佳選擇 """ 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 __str__(self): return self.show() 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): """洗牌(隨機(jī)亂序)""" random.shuffle(self.cards) self.index = 0 def deal(self): """發(fā)牌""" 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(): """主函數(shù)""" 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()
說(shuō)明:上面的代碼中使用了Emoji字符來(lái)表示撲克牌的四種花色占哟,在某些不支持Emoji字符的系統(tǒng)上可能無(wú)法顯示榨乎。
對(duì)象的復(fù)制(深復(fù)制/深拷貝/深度克隆和淺復(fù)制/淺拷貝/影子克隆)
-
垃圾回收史煎、循環(huán)引用和弱引用
Python使用了自動(dòng)化內(nèi)存管理,這種管理機(jī)制以引用計(jì)數(shù)為基礎(chǔ)驳糯,同時(shí)也引入了標(biāo)記-清除和分代收集兩種機(jī)制為輔的策略篇梭。
typedef struct_object { /* 引用計(jì)數(shù) */ int ob_refcnt; /* 對(duì)象指針 */ struct_typeobject *ob_type; } PyObject;
/* 增加引用計(jì)數(shù)的宏定義 */ #define Py_INCREF(op) ((op)->ob_refcnt++) /* 減少引用計(jì)數(shù)的宏定義 */ #define Py_DECREF(op) \ //減少計(jì)數(shù) if (--(op)->ob_refcnt != 0) \ ; \ else \ __Py_Dealloc((PyObject *)(op))
導(dǎo)致引用計(jì)數(shù)+1的情況:
- 對(duì)象被創(chuàng)建,例如
a = 23
- 對(duì)象被引用酝枢,例如
b = a
- 對(duì)象被作為參數(shù)恬偷,傳入到一個(gè)函數(shù)中帘睦,例如
f(a)
- 對(duì)象作為一個(gè)元素袍患,存儲(chǔ)在容器中,例如
list1 = [a, a]
導(dǎo)致引用計(jì)數(shù)-1的情況:
- 對(duì)象的別名被顯式銷(xiāo)毀竣付,例如
del a
- 對(duì)象的別名被賦予新的對(duì)象诡延,例如
a = 24
- 一個(gè)對(duì)象離開(kāi)它的作用域,例如f函數(shù)執(zhí)行完畢時(shí)古胆,f函數(shù)中的局部變量(全局變量不會(huì))
- 對(duì)象所在的容器被銷(xiāo)毀肆良,或從容器中刪除對(duì)象
引用計(jì)數(shù)可能會(huì)導(dǎo)致循環(huán)引用問(wèn)題筛璧,而循環(huán)引用會(huì)導(dǎo)致內(nèi)存泄露,如下面的代碼所示惹恃。為了解決這個(gè)問(wèn)題夭谤,Python中引入了“標(biāo)記-清除”和“分代收集”。在創(chuàng)建一個(gè)對(duì)象的時(shí)候巫糙,對(duì)象被放在第一代中朗儒,如果在第一代的垃圾檢查中對(duì)象存活了下來(lái),該對(duì)象就會(huì)被放到第二代中参淹,同理在第二代的垃圾檢查中對(duì)象存活下來(lái)采蚀,該對(duì)象就會(huì)被放到第三代中。
# 循環(huán)引用會(huì)導(dǎo)致內(nèi)存泄露 - Python除了引用技術(shù)還引入了標(biāo)記清理和分代回收 # 在Python 3.6以前如果重寫(xiě)__del__魔術(shù)方法會(huì)導(dǎo)致循環(huán)引用處理失效 # 如果不想造成循環(huán)引用可以使用弱引用 list1 = [] list2 = [] list1.append(list2) list2.append(list1)
以下情況會(huì)導(dǎo)致垃圾回收:
- 調(diào)用
gc.collect()
- gc模塊的計(jì)數(shù)器達(dá)到閥值
- 程序退出
如果循環(huán)引用中兩個(gè)對(duì)象都定義了
__del__
方法承二,gc模塊不會(huì)銷(xiāo)毀這些不可達(dá)對(duì)象榆鼠,因?yàn)間c模塊不知道應(yīng)該先調(diào)用哪個(gè)對(duì)象的__del__
方法,這個(gè)問(wèn)題在Python 3.6中得到了解決亥鸠。也可以通過(guò)
weakref
模塊構(gòu)造弱引用的方式來(lái)解決循環(huán)引用的問(wèn)題妆够。 - 對(duì)象被創(chuàng)建,例如
-
魔法屬性和方法(請(qǐng)參考《Python魔法方法指南》)
有幾個(gè)小問(wèn)題請(qǐng)大家思考:
- 自定義的對(duì)象能不能使用運(yùn)算符做運(yùn)算?
- 自定義的對(duì)象能不能放到set中负蚊?能去重嗎神妹?
- 自定義的對(duì)象能不能作為dict的鍵?
- 自定義的對(duì)象能不能使用上下文語(yǔ)法家妆?
-
混入(Mixin)
例子:自定義字典限制只有在指定的key不存在時(shí)才能在字典中設(shè)置鍵值對(duì)鸵荠。
class SetOnceMappingMixin(): """自定義混入類(lèi)""" __slots__ = () def __setitem__(self, key, value): if key in self: raise KeyError(str(key) + ' already set') return super().__setitem__(key, value) class SetOnceDict(SetOnceMappingMixin, dict): """自定義字典""" pass my_dict= SetOnceDict() try: my_dict['username'] = 'jackfrued' my_dict['username'] = 'hellokitty' except KeyError: pass print(my_dict)
-
元編程和元類(lèi)
例子:用元類(lèi)實(shí)現(xiàn)單例模式。
import threading class SingletonMeta(type): """自定義元類(lèi)""" def __init__(cls, *args, **kwargs): cls.__instance = None cls.__lock = threading.Lock() super().__init__(*args, **kwargs) def __call__(cls, *args, **kwargs): if cls.__instance is None: with cls.__lock: if cls.__instance is None: cls.__instance = super().__call__(*args, **kwargs) return cls.__instance class President(metaclass=SingletonMeta): """總統(tǒng)(單例類(lèi))""" pass
-
面向?qū)ο笤O(shè)計(jì)原則
- 單一職責(zé)原則 (SRP)- 一個(gè)類(lèi)只做該做的事情(類(lèi)的設(shè)計(jì)要高內(nèi)聚)
- 開(kāi)閉原則 (OCP)- 軟件實(shí)體應(yīng)該對(duì)擴(kuò)展開(kāi)發(fā)對(duì)修改關(guān)閉
- 依賴倒轉(zhuǎn)原則(DIP)- 面向抽象編程(在弱類(lèi)型語(yǔ)言中已經(jīng)被弱化)
- 里氏替換原則(LSP) - 任何時(shí)候可以用子類(lèi)對(duì)象替換掉父類(lèi)對(duì)象
- 接口隔離原則(ISP)- 接口要小而專(zhuān)不要大而全(Python中沒(méi)有接口的概念)
- 合成聚合復(fù)用原則(CARP) - 優(yōu)先使用強(qiáng)關(guān)聯(lián)關(guān)系而不是繼承關(guān)系復(fù)用代碼
- 最少知識(shí)原則(迪米特法則伤极,LoD)- 不要給沒(méi)有必然聯(lián)系的對(duì)象發(fā)消息
說(shuō)明:上面加粗的字母放在一起稱為面向?qū)ο蟮?strong>SOLID原則蛹找。
-
GoF設(shè)計(jì)模式
- 創(chuàng)建型模式:?jiǎn)卫⒐S哨坪、建造者庸疾、原型
- 結(jié)構(gòu)型模式:適配器、門(mén)面(外觀)当编、代理
- 行為型模式:迭代器届慈、觀察者、狀態(tài)忿偷、策略
例子:可插拔的哈希算法金顿。
class StreamHasher(): """哈希摘要生成器(策略模式)""" def __init__(self, alg='md5', size=4096): self.size = size alg = alg.lower() self.hasher = getattr(__import__('hashlib'), alg.lower())() def __call__(self, stream): return self.to_digest(stream) def to_digest(self, stream): """生成十六進(jìn)制形式的摘要""" for buf in iter(lambda: stream.read(self.size), b''): self.hasher.update(buf) return self.hasher.hexdigest() def main(): """主函數(shù)""" hasher1 = StreamHasher() with open('Python-3.7.1.tgz', 'rb') as stream: print(hasher1.to_digest(stream)) hasher2 = StreamHasher('sha1') with open('Python-3.7.1.tgz', 'rb') as stream: print(hasher2(stream)) if __name__ == '__main__': main()
-
-
迭代器和生成器
和迭代器相關(guān)的魔術(shù)方法(
__iter__
和__next__
)-
兩種創(chuàng)建生成器的方式(生成器表達(dá)式和
yield
關(guān)鍵字)def fib(num): """生成器""" a, b = 0, 1 for _ in range(num): a, b = b, a + b yield a 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()
-
并發(fā)編程
Python中實(shí)現(xiàn)并發(fā)編程的三種方案:多線程、多進(jìn)程和異步I/O鲤桥。并發(fā)編程的好處在于可以提升程序的執(zhí)行效率以及改善用戶體驗(yàn)揍拆;壞處在于并發(fā)的程序不容易開(kāi)發(fā)和調(diào)試,同時(shí)對(duì)其他程序來(lái)說(shuō)它并不友好芜壁。
-
多線程:Python中提供了Thread類(lèi)并輔以Lock礁凡、Condition、Event慧妄、Semaphore和Barrier顷牌。Python中有GIL來(lái)防止多個(gè)線程同時(shí)執(zhí)行本地字節(jié)碼,這個(gè)鎖對(duì)于CPython是必須的塞淹,因?yàn)镃Python的內(nèi)存管理并不是線程安全的窟蓝,因?yàn)镚IL的存在多線程并不能發(fā)揮CPU的多核特性。
""" 面試題:進(jìn)程和線程的區(qū)別和聯(lián)系饱普? 進(jìn)程 - 操作系統(tǒng)分配內(nèi)存的基本單位 - 一個(gè)進(jìn)程可以包含一個(gè)或多個(gè)線程 線程 - 操作系統(tǒng)分配CPU的基本單位 并發(fā)編程(concurrent programming) 1. 提升執(zhí)行性能 - 讓程序中沒(méi)有因果關(guān)系的部分可以并發(fā)的執(zhí)行 2. 改善用戶體驗(yàn) - 讓耗時(shí)間的操作不會(huì)造成程序的假死 """ 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(): """主函數(shù)""" if not os.path.exists(PREFIX): os.mkdir(PREFIX) for infile in glob.glob('images/*.png'): for size in (32, 64, 128): # 創(chuàng)建并啟動(dòng)線程 threading.Thread( target=generate_thumbnail, args=(infile, (size, size)) ).start() if __name__ == '__main__': main()
多個(gè)線程競(jìng)爭(zhēng)資源的情況
""" 多線程程序如果沒(méi)有競(jìng)爭(zhēng)資源處理起來(lái)通常也比較簡(jiǎn)單 當(dāng)多個(gè)線程競(jìng)爭(zhēng)臨界資源的時(shí)候如果缺乏必要的保護(hù)措施就會(huì)導(dǎo)致數(shù)據(jù)錯(cuò)亂 說(shuō)明:臨界資源就是被多個(gè)線程競(jìng)爭(zhēng)的資源 """ 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): # 通過(guò)鎖保護(hù)臨界資源 with self.lock: new_balance = self.balance + money time.sleep(0.001) self.balance = new_balance class AddMoneyThread(threading.Thread): """自定義線程類(lèi)""" def __init__(self, account, money): self.account = account self.money = money # 自定義線程的初始化方法中必須調(diào)用父類(lèi)的初始化方法 super().__init__() def run(self): # 線程啟動(dòng)之后要執(zhí)行的操作 self.account.deposit(self.money) def main(): """主函數(shù)""" account = Account() # 創(chuàng)建線程池 pool = ThreadPoolExecutor(max_workers=10) futures = [] for _ in range(100): # 創(chuàng)建線程的第1種方式 # threading.Thread( # target=account.deposit, args=(1, ) # ).start() # 創(chuàng)建線程的第2種方式 # AddMoneyThread(account, 1).start() # 創(chuàng)建線程的第3種方式 # 調(diào)用線程池中的線程來(lái)執(zhí)行特定的任務(wù) future = pool.submit(account.deposit, 1) futures.append(future) # 關(guān)閉線程池 pool.shutdown() for future in futures: future.result() print(account.balance) if __name__ == '__main__': main()
修改上面的程序运挫,啟動(dòng)5個(gè)線程向賬戶中存錢(qián),5個(gè)線程從賬戶中取錢(qián)套耕,取錢(qián)時(shí)如果余額不足就暫停線程進(jìn)行等待谁帕。為了達(dá)到上述目標(biāo),需要對(duì)存錢(qián)和取錢(qián)的線程進(jìn)行調(diào)度冯袍,在余額不足時(shí)取錢(qián)的線程暫停并釋放鎖匈挖,而存錢(qián)的線程將錢(qián)存入后要通知取錢(qián)的線程,使其從暫停狀態(tài)被喚醒康愤±苎可以使用
threading
模塊的Condition來(lái)實(shí)現(xiàn)線程調(diào)度,該對(duì)象也是基于鎖來(lái)創(chuàng)建的征冷,代碼如下所示:""" 多個(gè)線程競(jìng)爭(zhēng)一個(gè)資源 - 保護(hù)臨界資源 - 鎖(Lock/RLock) 多個(gè)線程競(jìng)爭(zhēng)多個(gè)資源(線程數(shù)>資源數(shù)) - 信號(hào)量(Semaphore) 多個(gè)線程的調(diào)度 - 暫停線程執(zhí)行/喚醒等待中的線程 - 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.Lock() self.condition = threading.Condition(lock) def withdraw(self, money): """取錢(qián)""" 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): """存錢(qián)""" 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=10) as pool: for _ in range(5): pool.submit(add_money, account) pool.submit(sub_money, account) if __name__ == '__main__': main()
-
多進(jìn)程:多進(jìn)程可以有效的解決GIL的問(wèn)題择膝,實(shí)現(xiàn)多進(jìn)程主要的類(lèi)是Process,其他輔助的類(lèi)跟threading模塊中的類(lèi)似检激,進(jìn)程間共享數(shù)據(jù)可以使用管道肴捉、套接字等,在multiprocessing模塊中有一個(gè)Queue類(lèi)叔收,它基于管道和鎖機(jī)制提供了多個(gè)進(jìn)程共享的隊(duì)列每庆。下面是官方文檔上關(guān)于多進(jìn)程和進(jìn)程池的一個(gè)示例。
""" 多進(jìn)程和進(jìn)程池的使用 多線程因?yàn)镚IL的存在不能夠發(fā)揮CPU的多核特性 對(duì)于計(jì)算密集型任務(wù)應(yīng)該考慮使用多進(jìn)程 time python3 example22.py real 0m11.512s user 0m39.319s sys 0m0.169s 使用多進(jìn)程后實(shí)際執(zhí)行時(shí)間為11.512秒今穿,而用戶時(shí)間39.319秒約為實(shí)際執(zhí)行時(shí)間的4倍 這就證明我們的程序通過(guò)多進(jìn)程使用了CPU的多核特性缤灵,而且這臺(tái)計(jì)算機(jī)配置了4核的CPU """ import concurrent.futures import math PRIMES = [ 1116281, 1297337, 104395303, 472882027, 533000389, 817504243, 982451653, 112272535095293, 112582705942171, 112272535095293, 115280095190773, 115797848077099, 1099726899285419 ] * 5 def is_prime(n): """判斷素?cái)?shù)""" if n % 2 == 0: return False sqrt_n = int(math.floor(math.sqrt(n))) for i in range(3, sqrt_n + 1, 2): if n % i == 0: return False return True def main(): """主函數(shù)""" with concurrent.futures.ProcessPoolExecutor() as executor: for number, prime in zip(PRIMES, executor.map(is_prime, PRIMES)): print('%d is prime: %s' % (number, prime)) if __name__ == '__main__': main()
說(shuō)明:多線程和多進(jìn)程的比較。
以下情況需要使用多線程:
- 程序需要維護(hù)許多共享的狀態(tài)(尤其是可變狀態(tài))蓝晒,Python中的列表腮出、字典、集合都是線程安全的芝薇,所以使用線程而不是進(jìn)程維護(hù)共享狀態(tài)的代價(jià)相對(duì)較小胚嘲。
- 程序會(huì)花費(fèi)大量時(shí)間在I/O操作上,沒(méi)有太多并行計(jì)算的需求且不需占用太多的內(nèi)存洛二。
以下情況需要使用多進(jìn)程:
- 程序執(zhí)行計(jì)算密集型任務(wù)(如:字節(jié)碼操作馋劈、數(shù)據(jù)處理攻锰、科學(xué)計(jì)算)。
- 程序的輸入可以并行的分成塊妓雾,并且可以將運(yùn)算結(jié)果合并娶吞。
- 程序在內(nèi)存使用方面沒(méi)有任何限制且不強(qiáng)依賴于I/O操作(如:讀寫(xiě)文件、套接字等)械姻。
-
異步處理:從調(diào)度程序的任務(wù)隊(duì)列中挑選任務(wù)妒蛇,該調(diào)度程序以交叉的形式執(zhí)行這些任務(wù),我們并不能保證任務(wù)將以某種順序去執(zhí)行楷拳,因?yàn)閳?zhí)行順序取決于隊(duì)列中的一項(xiàng)任務(wù)是否愿意將CPU處理時(shí)間讓位給另一項(xiàng)任務(wù)绣夺。異步任務(wù)通常通過(guò)多任務(wù)協(xié)作處理的方式來(lái)實(shí)現(xiàn),由于執(zhí)行時(shí)間和順序的不確定,因此需要通過(guò)回調(diào)式編程或者
future
對(duì)象來(lái)獲取任務(wù)執(zhí)行的結(jié)果。Python 3通過(guò)asyncio
模塊和await
和async
關(guān)鍵字(在Python 3.7中正式被列為關(guān)鍵字)來(lái)支持異步處理蔽豺。""" 異步I/O - async / await """ import asyncio def num_generator(m, n): """指定范圍的數(shù)字生成器""" yield from range(m, n + 1) async def prime_filter(m, n): """素?cái)?shù)過(guò)濾器""" primes = [] for i in num_generator(m, n): flag = True for j in range(2, int(i ** 0.5 + 1)): if i % j == 0: flag = False break if flag: print('Prime =>', i) primes.append(i) await asyncio.sleep(0.001) return tuple(primes) async def square_mapper(m, n): """平方映射器""" squares = [] for i in num_generator(m, n): print('Square =>', i * i) squares.append(i * i) await asyncio.sleep(0.001) return squares def main(): """主函數(shù)""" loop = asyncio.get_event_loop() future = asyncio.gather(prime_filter(2, 100), square_mapper(1, 100)) future.add_done_callback(lambda x: print(x.result())) loop.run_until_complete(future) loop.close() if __name__ == '__main__': main()
說(shuō)明:上面的代碼使用
get_event_loop
函數(shù)獲得系統(tǒng)默認(rèn)的事件循環(huán)瑞筐,通過(guò)gather
函數(shù)可以獲得一個(gè)future
對(duì)象,future
對(duì)象的add_done_callback
可以添加執(zhí)行完成時(shí)的回調(diào)函數(shù),loop
對(duì)象的run_until_complete
方法可以等待通過(guò)future
對(duì)象獲得協(xié)程執(zhí)行結(jié)果。Python中有一個(gè)名為
aiohttp
的三方庫(kù),它提供了異步的HTTP客戶端和服務(wù)器棵磷,這個(gè)三方庫(kù)可以跟asyncio
模塊一起工作,并提供了對(duì)Future
對(duì)象的支持晋涣。Python 3.6中引入了async和await來(lái)定義異步執(zhí)行的函數(shù)以及創(chuàng)建異步上下文仪媒,在Python 3.7中它們正式成為了關(guān)鍵字。下面的代碼異步的從5個(gè)URL中獲取頁(yè)面并通過(guò)正則表達(dá)式的命名捕獲組提取了網(wǎng)站的標(biāo)題谢鹊。import asyncio import re import aiohttp PATTERN = re.compile(r'\<title\>(?P<title>.*)\<\/title\>') async def fetch_page(session, url): async with session.get(url, ssl=False) as resp: return await resp.text() async def show_title(url): async with aiohttp.ClientSession() as session: html = await fetch_page(session, url) print(PATTERN.search(html).group('title')) def main(): urls = ('https://www.python.org/', 'https://git-scm.com/', 'https://www.jd.com/', 'https://www.taobao.com/', 'https://www.douban.com/') loop = asyncio.get_event_loop() tasks = [show_title(url) for url in urls] loop.run_until_complete(asyncio.wait(tasks)) loop.close() if __name__ == '__main__': main()
說(shuō)明:異步I/O與多進(jìn)程的比較算吩。
當(dāng)程序不需要真正的并發(fā)性或并行性,而是更多的依賴于異步處理和回調(diào)時(shí)佃扼,asyncio就是一種很好的選擇偎巢。如果程序中有大量的等待與休眠時(shí),也應(yīng)該考慮asyncio兼耀,它很適合編寫(xiě)沒(méi)有實(shí)時(shí)數(shù)據(jù)處理需求的Web應(yīng)用服務(wù)器压昼。
Python還有很多用于處理并行任務(wù)的三方庫(kù),例如:joblib瘤运、PyMP等窍霞。實(shí)際開(kāi)發(fā)中,要提升系統(tǒng)的可擴(kuò)展性和并發(fā)性通常有垂直擴(kuò)展(增加單個(gè)節(jié)點(diǎn)的處理能力)和水平擴(kuò)展(將單個(gè)節(jié)點(diǎn)變成多個(gè)節(jié)點(diǎn))兩種做法拯坟〉穑可以通過(guò)消息隊(duì)列來(lái)實(shí)現(xiàn)應(yīng)用程序的解耦合,消息隊(duì)列相當(dāng)于是多線程同步隊(duì)列的擴(kuò)展版本郁季,不同機(jī)器上的應(yīng)用程序相當(dāng)于就是線程冷溃,而共享的分布式消息隊(duì)列就是原來(lái)程序中的Queue钱磅。消息隊(duì)列(面向消息的中間件)的最流行和最標(biāo)準(zhǔn)化的實(shí)現(xiàn)是AMQP(高級(jí)消息隊(duì)列協(xié)議),AMQP源于金融行業(yè)似枕,提供了排隊(duì)盖淡、路由、可靠傳輸菠净、安全等功能禁舷,最著名的實(shí)現(xiàn)包括:Apache的ActiveMQ彪杉、RabbitMQ等毅往。
要實(shí)現(xiàn)任務(wù)的異步化,可以使用名為Celery的三方庫(kù)派近。Celery是Python編寫(xiě)的分布式任務(wù)隊(duì)列攀唯,它使用分布式消息進(jìn)行工作,可以基于RabbitMQ或Redis來(lái)作為后端的消息代理渴丸。
-