Python后端面試
Python后端技術(shù)棧
Web請(qǐng)求的流程
瀏覽器
負(fù)載均衡
Web框架
業(yè)務(wù)邏輯
數(shù)據(jù)庫緩存
Python語言基礎(chǔ)
語言特點(diǎn)
語法基礎(chǔ)
高級(jí)特性
算法與數(shù)據(jù)結(jié)構(gòu)
常用算法和數(shù)據(jù)結(jié)構(gòu)
分析時(shí)間、控件復(fù)雜度
實(shí)現(xiàn)常見數(shù)據(jù)結(jié)構(gòu)和算法
編程范式
面向?qū)ο缶幊?/p>
常用設(shè)計(jì)模式
函數(shù)式編程
操作系統(tǒng)
常用Linux命令
進(jìn)程、線程
內(nèi)存管理
網(wǎng)絡(luò)編程
常用協(xié)議TCP、IP、HTTP
Socket編程基礎(chǔ)
Python并發(fā)庫
數(shù)據(jù)庫
Mysql常考,索引優(yōu)化
關(guān)系型和NoSQL的使用場(chǎng)景
Redis緩存
Python Web框架
常用框架對(duì)比,RESTful
WSGI原理
Web安全問題
系統(tǒng)設(shè)計(jì)
設(shè)計(jì)原則,如何分析
后端系統(tǒng)常用組件(緩存、數(shù)據(jù)庫、消息隊(duì)列等)
技術(shù)選型和實(shí)現(xiàn)(短網(wǎng)址服務(wù)、Feed流系統(tǒng))
技術(shù)之外,軟實(shí)力
學(xué)習(xí)能力
業(yè)務(wù)理解能力,溝通交流能力
心態(tài)
Python初、中級(jí)工程師技能要求
初級(jí)工程師
扎實(shí)的計(jì)算機(jī)理論基礎(chǔ)
代碼規(guī)范,風(fēng)格良好
能在指導(dǎo)下靠譜地完成業(yè)務(wù)要求
中級(jí)工程師
扎實(shí)的計(jì)算機(jī)基礎(chǔ)和豐富的項(xiàng)目經(jīng)驗(yàn)
能夠獨(dú)立設(shè)計(jì)和完成項(xiàng)目要求
熟悉成員web組件(緩存、消息隊(duì)列),具有一定的系統(tǒng)設(shè)計(jì)能力
軟技能
具有產(chǎn)品意識(shí),技術(shù)引導(dǎo)產(chǎn)品
溝通交流嫩合理,團(tuán)隊(duì)協(xié)作能力
技術(shù)領(lǐng)導(dǎo)能力和影響力
面試準(zhǔn)備
工作內(nèi)容和業(yè)務(wù)緊密相關(guān)
平臺(tái)決定成長(zhǎng)(業(yè)務(wù)體量)
準(zhǔn)備面試需要有的放矢,跟職位相匹配
簡(jiǎn)歷書寫與自我介紹
簡(jiǎn)歷內(nèi)容
基本信息(姓名、學(xué)校、學(xué)歷、聯(lián)系方式等)
職業(yè)技能(編程語言、框架、數(shù)據(jù)庫、開發(fā)工具等)
關(guān)鍵項(xiàng)目經(jīng)驗(yàn)(擔(dān)任職責(zé),用到了哪些技術(shù))
簡(jiǎn)歷加分項(xiàng)
知名項(xiàng)目經(jīng)驗(yàn)
技術(shù)棧比較匹配
開源項(xiàng)目(GitHub、技術(shù)blog、Linux、unix geek)
簡(jiǎn)歷注意事項(xiàng)
內(nèi)容精簡(jiǎn)、突出重點(diǎn)。不宜超過兩頁、可以套用模板
主要格式,推薦PDF
信息真實(shí),不弄虛作假。技能要和崗位匹配
自我介紹
個(gè)人信息
掌握的技術(shù),參與過的項(xiàng)目
應(yīng)聘的崗位,表達(dá)對(duì)該崗位的看法和興趣
行為面試題與表達(dá)技巧
什么是行為面試
根據(jù)候選人過去的行為評(píng)測(cè)其勝任能力
理論依據(jù):行為的連貫性
人在面對(duì)相似的場(chǎng)景時(shí)會(huì)傾向于重復(fù)過去的行為模式
評(píng)判人的業(yè)務(wù)能力,溝通交流能力,語言表達(dá)能力,坑壓能力等
行為面試套路
提問方式:說說你曾經(jīng)
說說你做過的這個(gè)項(xiàng)目
說說你碰到過的技術(shù)難題?你是如何解決的?有哪些收獲?
STAR模型
面試官一般會(huì)問:你還有什么要問我的嗎?
千萬別說沒了,直接說表明你對(duì)崗位缺乏了解和興趣
表現(xiàn)出興趣:提問工作內(nèi)容(業(yè)務(wù))、技術(shù)棧、團(tuán)隊(duì)、項(xiàng)目等
問自己的感興趣的問題
注意事項(xiàng)
態(tài)度真誠,力求真實(shí),不要弄虛作假
言簡(jiǎn)意賅,突出重點(diǎn),省略細(xì)枝末節(jié)。適當(dāng)模擬訓(xùn)練
采用STAR模型讓回答更有條理
Python語言基礎(chǔ)常見考題
Python是靜態(tài)還是動(dòng)態(tài)類型?是強(qiáng)類型還是弱類型?
動(dòng)態(tài)強(qiáng)類型語言
動(dòng)態(tài)還是靜態(tài)指的是編譯期還是運(yùn)行期確定類型
強(qiáng)類型指的是不會(huì)發(fā)生隱式類型轉(zhuǎn)換
Python作為后端語言優(yōu)缺點(diǎn)
膠水語言,輪子多,應(yīng)用廣泛
語言靈活,生成力高
性能問題、代碼維護(hù)問題、Python2、3兼容問題
什么是鴨子類型
關(guān)注點(diǎn)在對(duì)象的行為,而不是類型(duck typing)
比如file,StringIO,socket對(duì)象都支持read/write方法
再比如定義了
__iter__
魔術(shù)方法的對(duì)象可以用for迭代
什么是monkey patch
所謂的monkey patch就是運(yùn)行時(shí)替換
比如gevent庫需要修改內(nèi)置的socket
from gevent import monkey; monkey.patch_socket()
什么是自省(Introspection)
運(yùn)行時(shí)判斷一個(gè)對(duì)象的類型的能力
Python一切皆對(duì)象,用type,id,isinstance獲取對(duì)象類型信息
Inspect模塊提供了更多獲取對(duì)象信息的函數(shù)
什么是列表和字典推導(dǎo)式(List Comprehension)
比如
[i for i in range(10) if i % 2 == 0]
一種快速生成list、dict、set的方式。用來替代map、filter等
(i for i in range(10))
返回生成器
Python2/3對(duì)比
Python3改進(jìn)
print成為函數(shù)
編碼問題。Python3不再有Unicode對(duì)象,默認(rèn)str就是unicode
除法變化。Python3返回浮點(diǎn)數(shù)
類型注解(type hint)。幫助IDE實(shí)現(xiàn)類型檢查
def hello(name: str) -> str:
return 'hello' + name
- 優(yōu)化的super()方便直接調(diào)用父類函數(shù)
class Base(object):
def hello(self):
print('say hello')
class C(Base):
def hello(self):
# py2
return super(C, self).hello()
class C2(Base):
def hello(self):
# py3
return super().hello()
高級(jí)解包操作。
a, b, *rest = range(10)
Keyword only arguments。限定關(guān)鍵字參數(shù)
def add(a, b, *, c):
print(a+b+c)
# 會(huì)報(bào)錯(cuò)
add(1, 2, 3)
# 正確寫法
add(1, 2, c=3)
Chained exceptions。Python3重新拋出異常不會(huì)丟失棧信息
一切返回迭代器range, zip, map, dict.values, etc. are all iterators.
生成的pyc文件統(tǒng)一放在
__pycache__
一些內(nèi)置庫的修改。urllib,selector等
性能優(yōu)化等
Python3新增
yield from鏈接子生成器
asyncio內(nèi)置庫,async/await原生協(xié)程支持異步編程
新的內(nèi)置庫enum,mock,asyncio,ipaddress,concurrent.futures等
Python2/3工具
six模塊
2to3等工具轉(zhuǎn)換代碼
__future__
Python函數(shù)常考題
以下Python代碼分別輸出什么?
def flist(l):
l.append(0)
print(l)
?
l = []
flist(l) #[0]
flist(l) #[0, 0]
def fstr(s):
s + = 'a'
print(s)
s = 'hehe'
fstr(s) #'hehea'
fstr(s) #'hehea'
可變類型參數(shù)
不可變類型參數(shù)
Python如何傳遞參數(shù)?
傳遞值還是引用呢?都不是。唯一支持的參數(shù)傳遞是共享傳參
Call by Object (Call by Object Reference or Call by sharing)
Call by sharing(共享傳參)。參數(shù)形參獲得實(shí)參中各個(gè)引用的副本
Python可變/不可變對(duì)象
不可變bool int float tuple str frozenset
可變list set dict
測(cè)試
# 一個(gè)小例題,請(qǐng)問這段代碼會(huì)輸出什么結(jié)果?
# first
def clear_list(l):
l = []
ll = [1, 2, 3]
clear_list(ll)
print(ll)
?
# second
# 默認(rèn)參數(shù)只計(jì)算一次
def flist(l=[1]):
l.append(1)
print(l)
flist()
flist()
Python *args, *kwargs
用來處理可變參數(shù)
*args
被打包成tuple**kwargs
被打包成dict
Python異常機(jī)制常考題
什么是Python異常?
-
Python使用異常處理錯(cuò)誤
-
BaseException
SystemExit/KeyboardInterrupt/GeneratorExit
Exception
-
什么時(shí)候需要捕獲處理異常?看Python內(nèi)置異常類型
網(wǎng)絡(luò)請(qǐng)求(超時(shí)、鏈接錯(cuò)誤等)
資源訪問(權(quán)限問題、資源不存在)
代碼邏輯(越界訪問、KeyError等)
如何處理Python異常
try:
# func # 可能會(huì)拋出異常的代碼
except (Exception1, Exception2) as e: # 可以捕獲多個(gè)異常并處理
# 異常處理代碼
else:
pass # 異常沒有發(fā)生的時(shí)候代碼邏輯
finally:
pass # 無論異常有沒有發(fā)生都會(huì)執(zhí)行的代碼,一般處理資源的關(guān)閉和釋放
如何自定義異常
繼承Exception實(shí)現(xiàn)自定義異常(想想為什么不是BaseException)
給異常加上一些附加信息
處理一些業(yè)務(wù)相關(guān)的特定異常(rasie MyException)
class MyException(Exception):
pass
?
try:
raise MyException('my exception')
except MyException as e:
print(e)
Python性能分析與優(yōu)化,GIL常考題
什么是Cpython GIL
GIL, Global Interpreter Lock
Cpython解釋器的內(nèi)存管理并不是線程安全的
保護(hù)多線程情況下Python對(duì)象的訪問
Cpython使用簡(jiǎn)單的鎖機(jī)制避免多個(gè)線程同時(shí)執(zhí)行字節(jié)碼
GIL影響
-
限制了程序的多核執(zhí)行
同一時(shí)間只能有一個(gè)線程執(zhí)行字節(jié)碼
CPU密集程序難以利用多核優(yōu)勢(shì)
IO期間會(huì)釋放GIL,對(duì)IO密集程序影響不大(爬蟲,web)
- image
如何規(guī)避GIL影響
CPU密集可以使用多進(jìn)程
IO密集可以使用多進(jìn)程、協(xié)程
cython擴(kuò)展
問題
import threading
?
n = [0]
?
def foo():
n[0] = n[0] + 1
n[0] = n[0] + 1
threads = []
for i in range(50000):
t = threading.Thread(target=foo)
threads.append(t)
for t in threads:
t.start()
print(n)
# 大多數(shù)情況下打印10000,偶爾打印9998,Python的多線程并不是絕對(duì)安全的
為什么有了GIL還要關(guān)注線程安全
-
Python中什么操作才是原子的?一步執(zhí)行到位
一個(gè)操作如果是一個(gè)字節(jié)碼指令可以執(zhí)行完成的就是原子的
原子的是可以保證線程安全的
使用dis操作才分析字節(jié)碼
import dis
?
def update_list(l):
# 原子操作,不用擔(dān)心線程安全問題
l[0] = 1
dis.dis(update_list)
'''
5 0 LOAD_CONST 1 (1)
2 LOAD_FAST 0 (l)
4 LOAD_CONST 2 (0)
6 STORE_SUBSCR # 字節(jié)碼操作,線程安全
8 LOAD_CONST 0 (None)
10 RETURN_VALUE
'''
?
def incr_list(l):
# 危險(xiǎn)!不是原子操作
l[0] += 1
dis.dis(incr_list)
'''
3 0 LOAD_FAST 0 (l)
2 LOAD_CONST 1 (0)
4 DUP_TOP_TWO
6 BINARY_SUBSCR
8 LOAD_CONST 2 (1)
10 INPLACE_ADD # 需要多個(gè)字節(jié)碼操作,有可能在線程執(zhí)行過程中切到其他線程
12 ROT_THREE
14 STORE_SUBSCR
16 LOAD_CONST 0 (None)
18 RETURN_VALUE
'''
如何剖析程序性能
-
使用各種profile工具(內(nèi)置或者第三方)
二八定律,大部分時(shí)間花費(fèi)在少量代碼上
內(nèi)置的profile、cprofile等工具
使用pyflame(uber開源)的火焰圖工具
服務(wù)端性能優(yōu)化措施
-
web應(yīng)用一般語言不會(huì)成為瓶頸
數(shù)據(jù)結(jié)構(gòu)和算法
數(shù)據(jù)庫層:索引優(yōu)化,慢查詢消除,批量操作減少IO,NoSQL
網(wǎng)絡(luò)IO:批量操作,pipeline操作減少IO
緩存:使用內(nèi)存數(shù)據(jù)庫redis/memcached
異步:asyncio,celery
并發(fā):gevent/多線程
Python生成器與協(xié)程
生成器(Generator)
- 生成器就是可以生成值的函數(shù)
- 當(dāng)一個(gè)函數(shù)里有了yield關(guān)鍵字就成了生成器
- 生成器可以掛起執(zhí)行并且保持當(dāng)前執(zhí)行狀態(tài)
def simple_gen():
yield 'hello'
yield 'world'
gen = simple_gen()
print(type(gen))
print(next(gen))
print(next(gen))
'''
<class 'generator'>
hello
world
'''
基于生成器的協(xié)程
- Python3之前沒有原生協(xié)程,只有基于生成器的協(xié)程
- PEP 342(Coroutines via Enhanced Generators)增強(qiáng)生成器功能
- 生成器可以通過yield暫停執(zhí)行和產(chǎn)出數(shù)據(jù)
- 同時(shí)支持send()向生成器發(fā)送數(shù)據(jù)和throw()向生成器拋出異常
def coro():
# yield關(guān)鍵字在=右邊作為表達(dá)式,可以被send值
hello = yield 'hello'
yield hello
c = coro()
# 輸出'hello',這里調(diào)用next產(chǎn)出第一個(gè)值'hello',之后函數(shù)暫停
print(next(c))
# 再次調(diào)用send發(fā)送值,此時(shí)hello變量賦值為'world',然后yield產(chǎn)出hello變量的值'world'
print(c.send('world'))
# 之后協(xié)程結(jié)束,后續(xù)再send值會(huì)拋出異常StopIteration
協(xié)程注意點(diǎn)
- 協(xié)程需要使用send(None)或者next(coroutine)來[預(yù)激](prime)laiqidong
- 在yield處協(xié)程會(huì)暫停執(zhí)行
- 單獨(dú)的yield value會(huì)產(chǎn)出值給調(diào)用方
- 可以通過coroutine.send(value)來給協(xié)程發(fā)送值,發(fā)送的值會(huì)賦值給yield左邊的變量value = yield
- 協(xié)程執(zhí)行完成后(沒有遇見下一個(gè)yield語句)會(huì)拋出StopIteration異常
協(xié)程裝飾器
- 避免每次都要用send預(yù)激它
from functools import wraps
def coroutine(func):
'''
裝飾器:向前執(zhí)行一個(gè)yield表達(dá)式,預(yù)激func
'''
@wraps(func)
def primer(*args, **kwargs):
gen = func(*args, **kwargs)
next(gen)
return gen
return primer
Python3.5引入async/await支持原生協(xié)程(native coroutine)
import asyncio
import datetime
import random
async def display_date(num, loop):
end_time = loop.time() + 50.0
while True:
print('Loop: {} Time: {}'.format(num, datetime.datetime.now()))
if (loop.time() + 1.0) >= end_time:
break
await asyncio.sleep(random.randint(0, 5))
loop = asyncio.get_event_loop()
asyncio.ensure_future(display_date(1, loop))
asyncio.ensure_future(display_date(2, loop))
loop.run_forever()
Python單元測(cè)試
什么是單元測(cè)試
- Unit Testing
- 針對(duì)程序模塊進(jìn)行正確性檢驗(yàn)
- 一個(gè)函數(shù),一個(gè)類進(jìn)行驗(yàn)證
- 自底向上保證程序正確性
為什么寫單元測(cè)試
- 三無代碼不可取(無文檔、無注釋、無單測(cè))
- 保證代碼邏輯的正確性(甚至有些采用測(cè)試驅(qū)動(dòng)開發(fā)(TDD))
- 單測(cè)影響設(shè)計(jì),易測(cè)的代碼往往是高內(nèi)聚低耦合的
- 回歸測(cè)試,防止改一處整個(gè)服務(wù)不可用
單元測(cè)試相關(guān)的庫
- nose/pytest較為常用
- mock模塊用來模擬替換網(wǎng)絡(luò)請(qǐng)求等
- coverage統(tǒng)計(jì)測(cè)試覆蓋率
def binary_search(arr, target):
if not arr:
return -1
low, high = 0, len(arr) - 1
while low <= high:
mid = low + (high - low) >> 2
if arr[mid] == target:
return mid
elif arr[mid] > target:
high = mid - 1
else:
low = mid + 1
return - 1
def test():
'''
如何設(shè)計(jì)測(cè)試用例:
- 正常值功能測(cè)試
- 邊界值(比如最大最小,最左最右)
- 異常值(比如None,空值,非法值)
'''
# 正常值,包含有和無兩種結(jié)果
assert binary_search([0, 1, 2, 3, 4, 5], 1) == 1
assert binary_search([0, 1, 2, 3, 4, 5], 6) == -1
assert binary_search([0, 1, 2, 3, 4, 5], -1) == -1
# 邊界值
assert binary_search([0, 1, 2, 3, 4, 5], 0) == 0
assert binary_search([0, 1, 2, 3, 4, 5], 5) == 5
assert binary_search([0], 0) == 0
# 異常值
assert binary_search([], 1) == -1
Python基礎(chǔ)練習(xí)題
Python深拷貝與淺拷貝
- 什么是深拷貝?什么是淺拷貝?
- Python中如何實(shí)現(xiàn)深拷貝?
- 思考:Python中如何正確初始化一個(gè)二維數(shù)組?
import copy
l1 = [1, 2, 3]
l2 = l1
l3 = copy.copy(l1)
l4 = copy.deepcopy(l1)
print(l1, l2, l3, l4)
'''
[1, 2, 3] [1, 2, 3] [1, 2, 3] [1, 2, 3]
'''
l2[0] = 666
print(l1, l2, l3, l4)
'''
[666, 2, 3] [666, 2, 3] [1, 2, 3] [1, 2, 3]
'''
l3[0] = 888
print(l1, l2, l3, l4)
'''
[666, 2, 3] [666, 2, 3] [888, 2, 3] [1, 2, 3]
'''
l4[0] = 999
print(l1, l2, l3, l4)
'''
[666, 2, 3] [666, 2, 3] [888, 2, 3] [999, 2, 3]
'''
Python內(nèi)置數(shù)據(jù)結(jié)構(gòu)和算法常考
數(shù)據(jù)結(jié)構(gòu)/算法 | 語言內(nèi)置 | 內(nèi)置庫 |
---|---|---|
線性結(jié)構(gòu) | list、tuple | array(數(shù)組,不常用)/collections.namedtuple |
鏈?zhǔn)浇Y(jié)構(gòu) | collections.deque(雙端隊(duì)列) | |
字典結(jié)構(gòu) | dict | collections.Counter(計(jì)數(shù)器)/OrderedDict(有序字典) |
集合結(jié)構(gòu) | set/frozenset(不可變集合) | |
排序算法 | sorted | |
二分算法 | bisect模塊 | |
堆算法 | heapq模塊 | |
緩存算法 | functools.lru_cache(Least Recent Used, python3) |
collections模塊提供了一些內(nèi)置數(shù)據(jù)結(jié)構(gòu)的擴(kuò)展
- namedtuple
import collections
Point = collections.namedtuple('Point', 'x, y')
p = Point(1, 2)
print(p.x)
'''
1
'''
print(p.y)
'''
2
'''
print(p.x == p[0])
'''
True
'''
- deque
import collections
de = collections.deque()
de.append(1)
de.appendleft(0)
print(de)
'''
deque([0, 1])
'''
de.pop()
'''
1
'''
de.popleft()
'''
0
'''
print(de)
'''
deque([])
'''
- Counter
- OrderedDict(使用循環(huán)雙端鏈表保存key的順序)
- defaultdict
Python dict底層結(jié)構(gòu)
- dict底層使用哈希表
- 為了支持快速查找使用了哈希表作為底層結(jié)構(gòu)
- 哈希表平均查找時(shí)間復(fù)雜度O(1)
- Cpython解釋器使用二次探查解決哈希沖突問題
Python list/tuple區(qū)別
- 都是線性結(jié)構(gòu),支持下標(biāo)訪問
- list是可變對(duì)象,tuple保存的引用不可變
t = [1,2], 1, 2
t[0].append(3)
print(t)
'''
([1, 2, 3], 1, 2)
'''
- list無法作為字典的key,tuple可以(可變對(duì)象不可hash)
什么是LRUCache?
-
Least-Recently-Used替換掉最近最少使用的對(duì)象
- 緩存剔除策略,當(dāng)緩存空間不夠用的時(shí)候需要一種方式剔除key
- 常見的有LRU, LFU等
- LRU通過使用一個(gè)循環(huán)雙端隊(duì)列不斷的把最新訪問的key放到表頭實(shí)現(xiàn)
-
字典用來緩存,循環(huán)雙端鏈表用來記錄訪問順序
- 利用Python內(nèi)置的dict+collections.OrderedDict實(shí)現(xiàn)
- dict用來當(dāng)做k/v鍵值對(duì)的緩存
- OrderedDict用來實(shí)現(xiàn)最新最近訪問的key
Python常考算法
排序+查找,重中之重
- 常考排序算法:冒泡排序、快速排序、歸并排序、堆排序
- 線性查找,二分查找
- 能獨(dú)立實(shí)現(xiàn)代碼(手寫),能夠分析時(shí)間空間復(fù)雜度
常見排序算法的時(shí)空復(fù)雜度
排序算法 | 最差時(shí)間復(fù)雜度 | 平均時(shí)間復(fù)雜度 | 穩(wěn)定度 | 空間復(fù)雜度 |
---|---|---|---|---|
冒泡排序 | O(n^2) | O(n^2) | 穩(wěn)定 | O(1) |
選擇排序 | O(n^2) | O(n^2) | 不穩(wěn)定 | O(1) |
插入排序 | O(n^2) | O(n^2) | 穩(wěn)定 | O(1) |
快速排序 | O(n^2) | O(n*log2n) | 不穩(wěn)定 | O(log2n)~O(n) |
堆排序 | O(n*log2n) | O(n*log2n) | 不穩(wěn)定 | O(1) |
排序算法的穩(wěn)定性
- 相同大小的元素在排序之后依然保持相對(duì)位置不變,就是穩(wěn)定的
-
r[i]=r[j]
且r[i]
在r[j]
之前,排序之后r[i]
依然在r[j]
之前 - 穩(wěn)定性對(duì)于排序一個(gè)復(fù)雜結(jié)構(gòu),并且需要保持原有排序才有意義
請(qǐng)寫出快速排序
- 快排經(jīng)常問:分治法(divide and conquer),快排三步走
- Partition:選擇基準(zhǔn)分割數(shù)組為兩個(gè)子數(shù)組,小于基準(zhǔn)和大于基準(zhǔn)的
- 對(duì)兩個(gè)子數(shù)組分別快排
- 合并結(jié)果
def quicksort(array):
# 遞歸出口
if len(array) < 2:
return array
else:
pivot_index = 0
pivot = array[pivot_index]
less_part = [i for i in array[pivot_index+1:] if i <= pivot]
great_part = [i for i in array[pivot_index+1:] if i > pivot]
return quicksort(less_part) + [pivot] + quicksort(great_part)
def test_quicksort():
import random
l = list(range(10))
random.shuffle(l)
print('排序前:', l)
res = quicksort(l)
print('排序后:', res)
assert res == sorted(l)
合并兩個(gè)有序數(shù)組
- 要求m+n復(fù)雜度內(nèi)
def merge_sorted_list(sorted_a, sorted_b):
len_a, len_b = len(sorted_a), len(sorted_b)
a = b = 0
res = []
while a < len_a and b < len_b:
if sorted_a[a] < sorted_b[b]:
res.append(sorted_a[a])
a += 1
else:
res.append(sorted_b[b])
b += 1
if a < len_a:
res.extend(sorted_a[a:])
else:
res.extend(sorted_b[b:])
return res
res = merge_sorted_list([0,3,6,7],[0, 0, 23,33])
print(res)
歸并排序
def mergesort(array):
# 遞歸出口
if len(array) <= 1:
return array
else:
mid = int(len(array)/2)
left_half = mergesort(array[:mid])
right_half = mergesort(array[mid:])
return merge_sorted_list(left_half, right_half)
堆排序
# 借助heapq模塊
def heapsort_use_heapq(iterable):
from heapq import heappush, heappop
items = []
for value in iterable:
heappush(items, value)
return [heappop(items) for i in range(len(items))]
二分查找
def binary_search(sorted_array, val):
if not sorted_array:
return -1
start = 0
end = len(sorted_array) - 1
while start <= end:
mid = start + ((end - start) >> 2)
if sorted_array[mid] == val:
return mid
elif sorted_array[mid] > val:
end = mid - 1
else:
start = mid + 1
return -1
Python常考數(shù)據(jù)結(jié)構(gòu)
Python web后端常考數(shù)據(jù)結(jié)構(gòu)
- 常見的數(shù)據(jù)結(jié)構(gòu)鏈表、隊(duì)列、棧、二叉樹、堆
- 使用內(nèi)置的結(jié)構(gòu)實(shí)現(xiàn)高級(jí)數(shù)據(jù)結(jié)構(gòu),比如內(nèi)置的list/deque實(shí)現(xiàn)棧
- LeetCode或者劍指Offer上的常考題
鏈表
鏈表有單鏈表、雙鏈表、循環(huán)雙端鏈表
如何使用Python來表示鏈表結(jié)構(gòu)
實(shí)現(xiàn)鏈表常見操作,比如插入節(jié)點(diǎn),反轉(zhuǎn)鏈表,合并多個(gè)鏈表等
-
LeetCode練習(xí)常見鏈表題目
-
206翻轉(zhuǎn)鏈表
class ListNode: def __init__(self, x): self.val = x self.next = None class Solution: def reverseList(self, head): ''' :type head: ListNode :rtype ListNode ''' pre, cur = None, head while cur: cur.next, pre, cur = pre, cur, cur.next return pre
-
隊(duì)列
隊(duì)列(queue)是先進(jìn)先出結(jié)構(gòu)
- 如何使用Python實(shí)現(xiàn)隊(duì)列?
- 實(shí)現(xiàn)隊(duì)列的append和pop操作,如何做到先進(jìn)先出
- 使用collections.deque實(shí)現(xiàn)隊(duì)列
from collections import deque
class Queue:
def __init__(self):
self.items = deque()
def append(self, val):
return self.items.append(val)
def pop(self):
return self.items.popleft()
def empty(self):
return len(self.items) == 0
棧
- 如何使用Python實(shí)現(xiàn)棧?
- 實(shí)現(xiàn)棧的push和pop操作,如何做到先進(jìn)后出
- 使用collections.deque實(shí)現(xiàn)隊(duì)列
from collections import deque
class Stack:
def __init__(self):
self.itmes = deque()
def push(self, val):
self.items.append(val)
def pop(self, val):
return self.items.pop()
def empty(self):
return len(self.items) == 0
字典與集合
Python dict/set底層都是哈希表
- 哈希表的實(shí)現(xiàn)原理,底層其實(shí)就是一個(gè)數(shù)組
- 根據(jù)哈希函數(shù)快速定位一個(gè)元素,平均查找O(1)
- 不斷加入元素會(huì)引起哈希表重新開辟空間,拷貝之前的元素到新數(shù)組
哈希表如何解決沖突
- 鏈接法
- 元素key沖突之后使用一個(gè)鏈表填充相同key的元素
- 開放尋址法
- 沖突之后根據(jù)一種方式(二次探查)尋找下一個(gè)可用的槽
- cpython使用的二次探查
二叉樹
先序、中序、后序
- 先序 根左右
- 中序 左根右
- 后序 左右根
class BinTreeNode:
def __init__(self, data, left=None, right=None):
self.data = data
self.left = left
self.right = right
def BinTree:
def __init__(self, root=None):
self.root = root
def preorder_trav(self, subtree):
'''先序'''
if subtree is not None:
print(subtree.data)
self.preorder_trav(subtree.left)
self.preorder_trav(subtree.right)
def inorder_trav(self, subtree):
if subtree is not None:
self.inorder_trav(subtree.left)
print(subtree.data)
self.inorder_trav(subtree.right)
def lastorder_trav(self, subtree):
if subtree is not None:
self.lastorder_trav(subtree.left)
self.lastorder_trav(subtree.right)
print(subtree.data)
堆
堆其實(shí)是完全二叉樹,有最大堆和最小堆
- 最大堆:對(duì)于每個(gè)非葉子節(jié)點(diǎn)V,V的值都比它的兩個(gè)孩子大
- 最小堆:對(duì)于每個(gè)非葉子節(jié)點(diǎn)V,V的值都比它的兩個(gè)孩子小
- 最大堆支持每次pop操作獲取最大的元素,最小堆獲取最小元素
- 常見問題:用堆完成topK問題,從海量數(shù)字中尋找最大的K個(gè)
import heapq
class TopK:
'''
獲取大量元素topK大個(gè)元素,固定內(nèi)存
思路:
1. 先放入元素前k個(gè)建立一個(gè)最小堆
2. 迭代剩余元素:
如果當(dāng)前元素小于堆頂元素,跳過該元素(肯定不是前k大)
否則替換堆頂元素為當(dāng)前元素,并重新調(diào)整堆
'''
def __init__(self, iterable, k):
self.miniheap = []
self.capacity = k
self.iterable = iterable
def push(self, val):
if len(self.miniheap) >= self.capacity:
min_val = self.miniheap[0]
if val > min_val:
heapq.heapreplace(self.miniheap, val)
else:
self.miniheap.append(val)
def get_topk(self):
for val in self.iterable:
self.push(val)
return self.miniheap
if __name__ == '__main__':
import random
l = list(range(10))
random.shuffle(l)
topk = TopK(l, 3)
res = topk.get_topk()
print(res)
Python白板編程
什么是白板編程
傳說中的手寫算法題,白紙或者白板上手寫代碼
- ACM/藍(lán)橋杯之類的算法競(jìng)賽
- 刷題,LeetCode、《劍指Offer》
為什么要手寫算法
工作用不到,為什么還要考?
- 有些公司為了篩選編程能力強(qiáng)的同學(xué),近年來對(duì)算法要求越來越高
- 針對(duì)剛出校門的同學(xué)問得多,有經(jīng)驗(yàn)的反而算法考的少(偏向工程經(jīng)驗(yàn))
- 競(jìng)爭(zhēng)越來越激烈,大家水平差不多的優(yōu)先選取有算法競(jìng)賽經(jīng)驗(yàn)的
如何準(zhǔn)備
沒有太好的方式,刷常見題。防止業(yè)務(wù)代碼寫多了算法手生
- 刷題,LeetCode,《劍指Offer》
- 面試之前系統(tǒng)整理之前做過的題目,不要靠記憶而是真正的理解掌握
- 打好基礎(chǔ)是重點(diǎn),面試可以刷常見題突擊,保持手感
面試前練習(xí)
刷題(LeetCode+劍指Offer+不同公司的面經(jīng))
- 《劍指Offer》上常見題用Python實(shí)現(xiàn)
- 把LeetCode上常見分類題目刷一遍(GitHub搜LeetCode分類)
- 常見排序算法和數(shù)據(jù)結(jié)構(gòu)可以手寫
Python常考數(shù)據(jù)結(jié)構(gòu)之鏈表
鏈表
鏈表涉及指針操作較為復(fù)雜,容易出錯(cuò),經(jīng)常用作考題
熟悉鏈表的定義和常見操作
-
常考題:刪除一個(gè)鏈表節(jié)點(diǎn)
-
LeetCode237
# Definition for singly-linked list. # class ListNode: # def __init__(self, x): # self.val = x # self.next = None class Solution: def deleteNode(self, node): """ :type node: ListNode :rtype: void Do not return anything, modify node in-place instead. """ node.val = node.next.val node.next = node.next.next
-
-
常考題:合并兩個(gè)有序鏈表
-
LeetCode21
# Definition for singly-linked list. # class ListNode: # def __init__(self, x): # self.val = x # self.next = None class Solution: def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode: res = ListNode(0) if l1 is None: return l2 if l2 is None: return l1 if l1.val < l2.val: res = l1 res.next = self.mergeTwoLists(l1.next, l2) else: res = l2 res.next = self.mergeTwoLists(l1, l2.next) return res
-
多寫多練
找到相關(guān)的題目,多做一些練習(xí)
- 一般一次很難寫對(duì)
- 嘗試自己先思考,先按照自己的方式寫代碼,提交后發(fā)現(xiàn)問題
- 如果實(shí)在沒有思路可以先去看其他人的題解
Python常考數(shù)據(jù)結(jié)構(gòu)之二叉樹
二叉樹
二叉樹涉及到遞歸和指針操作,常結(jié)合遞歸考察
二叉樹的操作很多可以用遞歸的方式解決,不了解遞歸會(huì)比較吃力
-
常考題:二叉樹的鏡像(反轉(zhuǎn)二叉樹)
-
LeetCode226
# Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None class Solution: def invertTree(self, root: TreeNode) -> TreeNode: if root: root.left, root.right = root.right, root.left self.invertTree(root.left) self.invertTree(root.right) return root
-
-
常考題:如何層序遍歷二叉樹(廣度優(yōu)先)
-
LeetCode102
# Definition for a binary tree node. # class TreeNode: # def __init__(self, x): # self.val = x # self.left = None # self.right = None class Solution: def levelOrder(self, root): """ :type root: TreeNode :rtype: List[List[int]] """ if not root: return [] res = [] cur_nodes = [root] next_nodes = [] res.append([i.val for i in cur_nodes]) while cur_nodes or next_nodes: for node in cur_nodes: if node.left: next_nodes.append(node.left) if node.right: next_nodes.append(node.right) if next_nodes: res.append([i.val for i in next_nodes]) cur_nodes = next_nodes next_nodes = [] return res
-
Python常考數(shù)據(jù)結(jié)構(gòu)之棧和隊(duì)列
棧和隊(duì)列
后進(jìn)先出(棧)vs 先進(jìn)先出(隊(duì)列)
熟練掌握用Python的list或者collections.deque()實(shí)現(xiàn)棧和隊(duì)列
-
常考題:用棧實(shí)現(xiàn)隊(duì)列
-
LeetCode232
class Stack: def __init__(self): self.stack = [] def push(self, x: int) -> None: self.stack.append(x) def pop(self): return self.stack.pop() def top(self): return self.stack[-1] def empty(self): return self.stack == [] class MyQueue: def __init__(self): """ Initialize your data structure here. """ self.s1 = Stack() self.s2 = Stack() def push(self, x: int) -> None: """ Push element x to the back of queue. """ self.s1.push(x) def pop(self) -> int: """ Removes the element from in front of queue and returns that element. """ if not self.s2.empty(): return self.s2.pop() while not self.s1.empty(): self.s2.push(self.s1.pop()) return self.s2.pop() def peek(self) -> int: """ Get the front element. """ if not self.s2.empty(): return self.s2.top() while not self.s1.empty(): self.s2.push(self.s1.pop()) return self.s2.top() def empty(self) -> bool: """ Returns whether the queue is empty. """ return self.s1.empty() and self.s2.empty() # Your MyQueue object will be instantiated and called as such: # obj = MyQueue() # obj.push(x) # param_2 = obj.pop() # param_3 = obj.peek() # param_4 = obj.empty()
-
Python常考數(shù)據(jù)結(jié)構(gòu)之堆
堆
堆的常考題基本圍繞在合并多個(gè)有序(數(shù)組/鏈表)、TopK問題
理解堆的概念,堆是完全二叉樹,有最大堆和最小堆
會(huì)使用Python內(nèi)置的heapq模塊實(shí)現(xiàn)堆的操作
-
常考題:合并k個(gè)有序鏈表
- LeetCode23
# Definition for singly-linked list. # class ListNode: # def __init__(self, x): # self.val = x # self.next = None class Solution: def mergeKLists(self, lists: List[ListNode]) -> ListNode: # 讀取所有節(jié)點(diǎn) h = [] for node in lists: while node: h.append(node.val) node = node.next if not h: return # 構(gòu)造一個(gè)最小堆 from heapq import heapify, heappop # 轉(zhuǎn)換成最小堆 heapify(h) # 構(gòu)造鏈表 root = ListNode(heappop(h)) curnode = root while h: nextnode = ListNode(heappop(h)) curnode.next = nextnode curnode = nextnode return root
Python常考數(shù)據(jù)結(jié)構(gòu)之字符串
字符串
了解字符串的常用操作
Python內(nèi)置了很多字符串操作,例如split、strip、upper、replace等
-
常考題:翻轉(zhuǎn)一個(gè)字符串
-
LeetCode344
class Solution: def reverseString(self, s: List[str]) -> None: """ Do not return anything, modify s in-place instead. """ start, end = 0, len(s)-1 while start < end: s[start], s[end] = s[end], s[start] start += 1 end -= 1
-
-
常考題:判斷一個(gè)字符串是否為回文
-
LeetCode9
class Solution: def isPalindrome(self, x: int) -> bool: if x < 0: return False s = str(x) start, end = 0, len(s)-1 while start < end: if s[start] == s[end]: start += 1 end -= 1 else: return False return True
-
算法與數(shù)據(jù)結(jié)構(gòu)練習(xí)題
反轉(zhuǎn)鏈表
鏈表在面試中是一個(gè)高頻考點(diǎn)
- 如何反轉(zhuǎn)一個(gè)單鏈表
- 能否用循環(huán)的方式實(shí)現(xiàn)嗎?
- 能否用遞歸的方式實(shí)現(xiàn)嗎?
面向?qū)ο蠡A(chǔ)及Python類常考問題
什么是面向?qū)ο缶幊?/h3>
Object Oriented Programming(OOP)
- 把對(duì)象作為基本單元,把對(duì)象抽象成類(Class),包含成員和方法
- 數(shù)據(jù)封裝、繼承、多態(tài)
- Python中使用類來實(shí)現(xiàn)。過程式編程(函數(shù)),OOP(類)
Python中如何創(chuàng)建類?
class Person(object): # py3可以省略object直接寫作 class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def print_name(self):
print('my name is {}'.format(self.name))
組合與繼承
優(yōu)先使用組合而非繼承
- 組合是使用其他的類實(shí)例作為自己的一個(gè)屬性(Has-a 關(guān)系)
- 子類繼承父類的屬性和方法(Is a 關(guān)系)
- 優(yōu)先使用組合保持代碼簡(jiǎn)單
類變量和實(shí)例變量的區(qū)別
區(qū)分類變量和實(shí)例變量
- 類變量由所有實(shí)例共享
- 實(shí)例變量由實(shí)例單獨(dú)享有,不同實(shí)例之間不影響
- 當(dāng)我們需要一個(gè)類的不同實(shí)例之間共享變量的時(shí)候使用類變量
classmethod和staticmethod區(qū)別
classmethod vs staticmethod
- 都可以通過Class.method()方式使用
- classmethod第一個(gè)參數(shù)是cls,可以引用類變量
- staticmethod使用起來和普通函數(shù)一樣,只不過放在類里
什么是元類?使用場(chǎng)景
元類(Meta Class)是創(chuàng)建類的類
- 元類允許我們控制類的生成,比如修改類的屬性等
- 使用type來定義元類
- 元類最常見的一個(gè)使用場(chǎng)景就是ORM框架
# 元類繼承自type
class LowercaseMeta(type):
'''修改類的屬性名稱為小寫的元類'''
def __new__(mcs, name, bases, attrs):
lower_attrs = {}
for k, v in attrs.items():
if not k.startswith('__'):
lower_attrs[k.lower()] = v
else:
lower_attrs[k] = v
return type.__new__(mcs, name, bases, lower_attrs)
class LowercaseClass(metaclass=LowercaseMeta):
BAR = True
def Hello(self):
print('hello')
print(dir(LowercaseClass))
Python裝飾器常見問題
Decorator
- Python中一切皆對(duì)象,函數(shù)也可以當(dāng)做參數(shù)傳遞
- 裝飾器是接受函數(shù)作為參數(shù),添加功能后返回一個(gè)新函數(shù)的函數(shù)(類)
- Python中通過@使用裝飾器
編寫一個(gè)計(jì)算耗時(shí)的裝飾器
import time
def log_time(func):
def wrapper(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
print('use time:{}'.format(time.time()-start))
return
return wrapper
@log_time
def mysleep():
time.sleep(1)
mysleep()
如何使用類編寫裝飾器?
import time
class LogTime:
def __call__(self, func):
def wrapper(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
print('use time. {}'.format(time.time()-start))
return res
return wrapper
@LogTime()
def mysleep2():
time.sleep(1)
mysleep2()
如何給裝飾器增加參數(shù)?
使用類裝飾器比較方便實(shí)現(xiàn)裝飾器參數(shù)
import time
class LogTimeParams:
def __init__(self, use_int=False):
self.use_int = use_int
def __call__(self, func):
def wrapper(*args, **kwargs):
start = time.time()
res = func(*args, **kwargs)
if self.use_int:
print('use time: {}'.format(int(time.time()-start)))
else:
print('use time: {}'.format(time.time()-start))
return res
return wrapper
@LogTimeParams(True)
def mysleep3():
time.sleep(1)
mysleep3()
Python設(shè)計(jì)模式之創(chuàng)建型模式
創(chuàng)建型設(shè)計(jì)模式常考題
常見創(chuàng)建型設(shè)計(jì)模式
- 工廠模式(Factory):解決對(duì)象創(chuàng)建問題
- 構(gòu)造模式(Builder):控制復(fù)雜對(duì)象的創(chuàng)建
- 原型模式(Prototype):通過原型的克隆創(chuàng)建新的實(shí)例
- 單例模式(Borg/Singleton):一個(gè)類只能創(chuàng)建同一個(gè)對(duì)象
- 對(duì)象池模式(Pool):預(yù)先分配同一類型的一組實(shí)例
- 惰性計(jì)算模式(Lazy Evaluation):延遲計(jì)算(Python的property)
工廠模式
什么是工廠模式(Factory)
- 解決對(duì)象的創(chuàng)建問題
- 解耦對(duì)象的創(chuàng)建和使用
- 包括工廠方法和抽象工廠
# 一個(gè)工廠方法的例子
class DogToy:
def speak(self):
print('wang wang')
class CatToy:
def speak(self):
print('miao miao')
def toy_factory(toy_type):
if toy_type == 'dog':
return DogToy()
else:
return CatToy()
構(gòu)造模式
什么是構(gòu)造模式(Builder)
- 用來控制復(fù)雜對(duì)象的構(gòu)造
- 創(chuàng)建和表示分離。比如你要買電腦,工廠模式直接給你需要的電腦
- 但是構(gòu)造模式允許你自己定義電腦的配置,組裝完成后給你
# 一個(gè)構(gòu)造模式的例子
class Computer:
def __init__(self, serial_number):
self.serial = serial_number
self.memory = None
self.hdd = None
self.gpu = None
def __str__(self):
info = ('Memory: {}GB'.format(self.memory),
'Hard Disk: {}'.format(self.hdd),
'Graphics Card: {}'.format(self.gpu))
return '\n'.join(info)
class ComputerBuilder:
def __init__(self):
self.computer = Computer('Macbook Pro 2018')
def configure_memory(self, amount):
self.computer.memory = amount
def configure_hdd(self, amount):
self.computer.hdd = amount
def configure_gpu(self, amount):
self.computer.gpu = amount
class HardwareEngineer:
def __init__(self):
self.builder = None
def construct_computer(self, memory, hdd, gpu):
self.builder = ComputerBuilder()
[step for step in (self.builder.configure_memory(memory),
self.builder.configure_hdd(hdd),
self.builder.configure_gpu(gpu))]
@property
def computer(self):
return self.builder.computer
# 使用builder,可以創(chuàng)建多個(gè)builder類實(shí)現(xiàn)不同的組裝方式
engineer = HardwareEngineer()
engineer.construct_computer(hdd=500, memory=8, gpu='GeForece GTX 650 Ti')
computer = engineer.computer
print(computer)
原型模式
什么是原型模式(Prototype)
- 通過克隆原型來創(chuàng)建新的實(shí)例
- 可以使用相同的原型,通過修改部分屬性來創(chuàng)建新的實(shí)例
- 用途:對(duì)于一些創(chuàng)建實(shí)例開銷比較高的地方可以用原型模式
單例模式
單例模式的實(shí)現(xiàn)有多種方式
- 單例模式:一個(gè)類創(chuàng)建出來的對(duì)象都是同一個(gè)
- Python的模塊其實(shí)就是單例,只會(huì)導(dǎo)入一次
- 使用共享同一個(gè)實(shí)例的方式來創(chuàng)建單例模式
# 單例模式
class Singleton:
def __new__(cls, *args, **kwargs):
if not hasattr(cls, '_instance'):
_instance = super().__new__(cls, *args, **kwargs)
cls._instance = _instance
return cls._instance
class MyClass(Singleton):
pass
c1 = MyClass()
c2 = MyClass()
assert c1 is c2 # 單例的,c1 c2是同一個(gè)實(shí)例
Python設(shè)計(jì)模式之結(jié)構(gòu)型模式
常見結(jié)構(gòu)型設(shè)計(jì)模式
- 裝飾器(Decorator):無需子類化擴(kuò)展對(duì)象功能
- 代理模式(Proxy):把一個(gè)對(duì)象的操作代理到另一個(gè)對(duì)象
- 適配器模式(Adapter):通過一個(gè)間接層適配統(tǒng)一接口
- 外觀模式(Facede):簡(jiǎn)化復(fù)雜對(duì)象的訪問問題
- 享元模式(Flyweight):通過對(duì)象復(fù)用(池)改善資源利用,比如連接池
- Model-View-Controller(MVC):解耦展示邏輯和和業(yè)務(wù)邏輯
代理模式
什么是代理模式(Proxy)
- 把一個(gè)對(duì)象的操作代理到另一個(gè)對(duì)象
- 這里又要提到我們之前實(shí)現(xiàn)的Stack/Queue,把操作代理到deque
- 通常使用has-a組合關(guān)系
from collections import deque
# 使用組合的例子
class Stack:
def __init__(self):
self._deque = deque() # has a deque()
def push(self, val):
return self._deque.append(val)
def pop(self):
return self._deque.pop()
def empty(self):
return len(self._deque) == 0
適配器模式
什么是適配器模式(Adapter)
- 把不同對(duì)象的接口適配到同一個(gè)接口
- 想象一個(gè)多功能充電頭,可以給不同的電器充電,充當(dāng)了適配器
- 當(dāng)我們需要給不同的對(duì)象統(tǒng)一接口的時(shí)候可以使用適配器模式
# 適配器模式的例子
class Dog:
def __init__(self):
self.name = 'Dog'
def bark(self):
return 'woof!'
class Cat:
def __init__(self):
self.name = 'Cat'
def meow(self):
return 'meow'
class Adapter:
def __init__(self, obj, **adapted_methods):
'''
We set the adapted methods in the object's dict
'''
self.obj = obj
self.__dict__.update(adapted_methods)
def __getattr__(self, attr):
'''
All non-adapted calls are passed to the object
'''
return getattr(self.obj, attr)
objects = []
dog = Dog()
objects.append(Adapter(dog, make_noise=dog.bark))
cat = Cat()
objects.append(Adapter(cat, make_noise=cat.meow))
for obj in objects:
print('A {0} goes {1}'.format(obj.name, obj.make_noise()))
Python設(shè)計(jì)模式之行為型模式
常見行為型設(shè)計(jì)模式
- 迭代器模式(Iterator):通過統(tǒng)一的接口迭代對(duì)象
- 觀察者模式(Observer):對(duì)象發(fā)生改變的時(shí)候,觀察者執(zhí)行相應(yīng)動(dòng)作
- 策略模式(Strategy):針對(duì)不同規(guī)模輸入使用不同的策略
迭代器模式
什么是迭代器模式(Iterator)
- Python內(nèi)置對(duì)迭代器模式的支持
- 比如我們可以用for遍歷各種Iterable的數(shù)據(jù)類型
- Python里可以實(shí)現(xiàn)
__next__
和__iter__
實(shí)現(xiàn)迭代器
from collections import deque
# 使用組合的例子
class Stack:
def __init__(self):
self._deque = deque() # has a deque()
def push(self, val):
return self._deque.append(val)
def pop(self):
return self._deque.pop()
def empty(self):
return len(self._deque) == 0
def __iter__(self):
res = []
for i in self._deque:
res.append(i)
for i in reversed(res):
yield i
s = Stack()
s.push(1)
s.push(2)
for i in s:
print(i)
觀察者模式
什么是觀察者模式(Observer)
- 發(fā)布訂閱是一種最常用的實(shí)現(xiàn)方式
- 發(fā)布訂閱用于解耦邏輯
- 可以通過回調(diào)等方式實(shí)現(xiàn),當(dāng)發(fā)生事件時(shí),調(diào)用相應(yīng)的回調(diào)函數(shù)
# 發(fā)布訂閱模式
# 發(fā)布者
class Publisher:
def __init__(self):
# 觀察者
self.observers = []
# 加入觀察者
def add(self, observer):
if observer not in self.observers:
self.observers.append(observer)
else:
print('Failed to add: {}'.format(observer))
# 移除觀察者
def remove(self, observer):
try:
self.observers.remove(observer)
except ValueError:
print('Failed to remove: {}'.format(observer))
# 調(diào)用觀察者的回調(diào)
def notify(self):
[o.notify_by(self) for o in self.observers]
# 繼承自發(fā)布者
class Formatter(Publisher):
def __init__(self, name):
super().__init__()
self.name = name
self._data = 0
@property
def data(self):
return self._data
@data.setter
def data(self, new_value):
self._data = int(new_value)
# data在被合法賦值以后會(huì)執(zhí)行notify
self.notify()
class BinaryFormatter:
'''訂閱者'''
def notify_by(self, publisher):
print("{}: '{}' has now bin data = {}".format(
type(self).__name__,
publisher.name,
bin(publisher.data))
)
# 發(fā)布者
df = Formatter('formatter')
# 訂閱者
bf = BinaryFormatter()
df.add(bf)
df.data = 3
策略模式
什么是策略模式(Strategy)
- 根據(jù)不同的輸入采用不同的策略
- 比如買東西超過10個(gè)大八折,超過20個(gè)打七折
- 對(duì)外暴露統(tǒng)一的接口,內(nèi)部采用不同的策略計(jì)算
# 策略模式
class Order:
def __init__(self, price, discount_strategy=None):
self.price = price
self.discount_strategy = discount_strategy
def price_after_discount(self):
if self.discount_strategy:
discount = self.discount_strategy(self)
else:
discount = 0
return self.price - discount
def __repr__(self):
fmt = '<Price: {}, price after discount: {}>'
return fmt.format(self.price, self.price_after_discount())
def ten_percent_discount(order):
return order.price * 0.10
def on_sale_discount(order):
return order.price * 0.25 + 20
order0 = Order(100)
order1 = Order(100, discount_strategy=ten_percent_discount)
order2 = Order(1000, discount_strategy=on_sale_discount)
print(order0)
print(order1)
print(order2)