Python后端面試(持續(xù)更新)

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ì)該崗位的看法和興趣

image

行為面試題與表達(dá)技巧

什么是行為面試

根據(jù)候選人過去的行為評(píng)測(cè)其勝任能力

  • 理論依據(jù):行為的連貫性

  • 人在面對(duì)相似的場(chǎng)景時(shí)會(huì)傾向于重復(fù)過去的行為模式

  • 評(píng)判人的業(yè)務(wù)能力,溝通交流能力,語言表達(dá)能力,坑壓能力等

行為面試套路

  • 提問方式:說說你曾經(jīng)

  • 說說你做過的這個(gè)項(xiàng)目

  • 說說你碰到過的技術(shù)難題?你是如何解決的?有哪些收獲?

STAR模型

image

面試官一般會(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ò)展

image

問題

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)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。