躬身入局,干貨分享,2023年春招后端技術(shù)崗(Python)面試實(shí)戰(zhàn)教程,Offer今始為君發(fā)

早春二月,研發(fā)倍忙,雜花生樹,群鷗竟飛。為什么?因?yàn)榇杭菊衅?,無論是應(yīng)屆生,還是職場(chǎng)老鳥,都在摩拳擦掌,秣馬厲兵,準(zhǔn)備在面試場(chǎng)上一較身手,既分高下,也決Offer,本次我們打響春招第一炮,躬身入局,讓2023年的第一個(gè)Offer來的比以往快那么一點(diǎn)點(diǎn)。

打開某垂直招聘平臺(tái),尋找2023年的第一個(gè)獵物:

image

投遞簡(jiǎn)歷之后,如約進(jìn)行面試。

筆試題

正規(guī)公司的面試一般都是筆試先行,筆試題的作用非常務(wù)實(shí),就是直接篩掉一批人,提高面試效率,需要注意的是,在這個(gè)環(huán)節(jié)中,往往無法用搜索引擎進(jìn)行檢索,所以,你的大腦就是Python解釋器,你的筆將會(huì)代替程序的輸出:

# 實(shí)現(xiàn)字符串反轉(zhuǎn),以逗號(hào)作為切割符,切割的子串以單詞作為單元反轉(zhuǎn)  
# 輸入:hello world, god bless you  
# 輸出:world hello, you bless god

這道題網(wǎng)上沒有原題,但其實(shí)并不難,考點(diǎn)在于應(yīng)聘者對(duì)于Python基礎(chǔ)和復(fù)合數(shù)據(jù)類型內(nèi)置方法的熟悉程度,題目中所謂的字符串反轉(zhuǎn)并不是真正意義的字符串反轉(zhuǎn),而是以單詞為單元的反轉(zhuǎn),同時(shí)加入了逗號(hào)分割邏輯,所以只要對(duì)字符串內(nèi)置方法split,rstrip和列表內(nèi)置方法join以及reverse的用法足夠了解,就可以直接寫出解法:

def reseverWords(s:str) ->  str:  
    all_str =   ""  
    s   =    s.split(',')  
    for x   in  s:  
        lis=    x.split()  
        lis.reverse()  
        all_str +=  ' '.join(lis)+', '  
    all_str=all_str.rstrip(', ')  
    return  all_str  
print(reseverWords(str1))

第二題是SQL語句題目,請(qǐng)寫一條sql,按照地區(qū)的分組聚合數(shù)據(jù)進(jìn)行排序:

id    name       location  
--    -----      --------  
1     Mark       US  
2     Mike       US  
3     Paul       Australia  
4     Pranshu    India  
5     Pranav     India  
6     John       Canada  
7     Rishab     India

排序后結(jié)果:

id    name       location  
--    -----      --------  
4     Pranshu    India  
5     Pranav     India  
7     Rishab     India  
1     Mark       US  
2     Mike       US  
3     Paul       Australia  
6     John       Canada

這道題也無法在網(wǎng)上查證,一般的分組聚合只是查一個(gè)數(shù),這個(gè)是按照數(shù)量進(jìn)行排序,并且其實(shí)并不展示數(shù)量,也可以理解為展示的為分組數(shù)據(jù)的明細(xì)排行榜:

SELECT x.*   
  FROM my_table x   
  JOIN (SELECT location, COUNT(*) total FROM my_table GROUP BY location) y  
    ON y.location = x.location  
 ORDER   
    BY total DESC  
     , id;

思路是先分組,隨后按照分組的聚合數(shù)據(jù)根據(jù)地區(qū)字段連表排序即可。

自我介紹

通過筆試題篩選后,進(jìn)入自我介紹環(huán)節(jié),一般介紹技術(shù)棧和簡(jiǎn)單的項(xiàng)目經(jīng)歷即可,參考示例:

您好(下午好/上午好),我是19年畢業(yè)的,在RD(Research and Development engineer即研發(fā)工程師崗位)崗差不多有三年左右的工作經(jīng)驗(yàn),一開始在一家創(chuàng)業(yè)型公司起步,當(dāng)時(shí)主力開發(fā)語言是python,,使用mtv架構(gòu),在公司主要和業(yè)務(wù)打交道,開發(fā)和維護(hù)后臺(tái)的API,大概沉淀了兩年左右吧,我跳槽到了第二家公司,薪酬實(shí)現(xiàn)了double,在新的技術(shù)團(tuán)隊(duì)里,我接觸到了前后端分離項(xiàng)目,也學(xué)習(xí)了異步編程思想,主力框架是tornado,前端技術(shù)也有所涉獵,比如vue框架,了解了數(shù)據(jù)雙向綁定理念,同時(shí)也學(xué)習(xí)了在業(yè)務(wù)解耦和服務(wù)封裝層面比較流行的docker容器技術(shù),這項(xiàng)技術(shù)使我平時(shí)開發(fā)和測(cè)試工作都提高了效率,最近一年左右吧,我經(jīng)常使用的web框架是tornado,這個(gè)框架我個(gè)人非常喜歡,它的異步非阻塞特性讓我對(duì)異步編程思想的認(rèn)識(shí)更深入了。我也嘗試過remote這種工作形式,也鍛煉了我在團(tuán)隊(duì)中的溝通能力,其實(shí)三四年下來,做過的東西解決過的問題也挺多的,待過大團(tuán)隊(duì)也經(jīng)歷過小團(tuán)隊(duì),給我的感覺就是互聯(lián)網(wǎng)企業(yè)隨著發(fā)展,技術(shù)和行業(yè)邊界其實(shí)是越來越模糊的,也就是說技術(shù)都是具有相通性的,我個(gè)人來講,優(yōu)勢(shì)就是技術(shù)涉獵比較廣,前后端都接觸過,踩得坑也比較多,在特定領(lǐng)域有一定的深入,比如異步編程這塊。另外我覺得搞開發(fā)的,學(xué)習(xí)能力,總結(jié)能力很重要,所以我一直保持著寫技術(shù)博客的習(xí)慣,這樣經(jīng)過沉淀,可以提高一個(gè)人的分析能力,也就是解決問題的能力,我的介紹完了,謝謝。

進(jìn)程、線程和協(xié)程的區(qū)別

進(jìn)程、線程和協(xié)程,從來就是Python面試中聚訟不休的一個(gè)話題,只要我們還在使用Python,就一定逃離不了三程問題:

進(jìn)程

首先明確一下進(jìn)程和線程的概念,進(jìn)程系統(tǒng)進(jìn)行資源分配的基本單位,一臺(tái)機(jī)器上可以有多個(gè)進(jìn)程,每個(gè)進(jìn)程執(zhí)行不同的程序,每個(gè)進(jìn)程相對(duì)獨(dú)立,擁有自己的內(nèi)存空間,隔離性和穩(wěn)定性比較高,進(jìn)程之間互不影響,但是資源共享相對(duì)麻煩,系統(tǒng)資源占用相對(duì)高,同時(shí)進(jìn)程可以利用cpu多核資源,適合cpu密集型任務(wù),比如一些統(tǒng)計(jì)計(jì)算任務(wù),比如計(jì)算廣告轉(zhuǎn)化率,uv、pv等等,或者一些視頻的壓縮解碼任務(wù),進(jìn)程還有一個(gè)使用場(chǎng)景,就是后期部署項(xiàng)目的時(shí)候,nginx反向代理后端服務(wù),往往需要開啟多個(gè)tornado服務(wù)來支持后臺(tái)的并發(fā),就是利用了多進(jìn)程的互不干擾,就算某個(gè)進(jìn)程僵死,也不會(huì)影響其他進(jìn)程,進(jìn)程使用的是mulitprossing庫 ,往往是先聲明進(jìn)程實(shí)例,里面可以傳入消費(fèi)方法名稱和不定長(zhǎng)參數(shù)args,然后將實(shí)例放入指定進(jìn)程數(shù)的容器中(list),通過循環(huán)或者列表推導(dǎo)式,使用start方法開啟進(jìn)程,join方法阻塞主進(jìn)程。

線程

線程是系統(tǒng)進(jìn)行資源調(diào)度的最小單位,它屬于進(jìn)程,每一個(gè)進(jìn)程中都會(huì)有一個(gè)線程,由于線程操作是單進(jìn)程的,所以線程之間可以共享內(nèi)存變量,互相通信非常方便,它的系統(tǒng)開銷比進(jìn)程小,它是線程之間由于共享內(nèi)存,會(huì)互相影響,如果一個(gè)線程僵死會(huì)影響其他線程,隔離性和穩(wěn)定性不如進(jìn)程,同時(shí),線程并不安全,如果對(duì)同一個(gè)對(duì)象進(jìn)行操作,需要手動(dòng)加鎖,另外從性能上講,多線程會(huì)觸發(fā)python的全局解釋器鎖,導(dǎo)致同一時(shí)間點(diǎn)只會(huì)有一個(gè)線程運(yùn)行的交替運(yùn)行模式,線程適用于io密集型任務(wù),所謂io密集型任務(wù)就是大量的硬盤讀寫操作或者網(wǎng)絡(luò)tcp通信的任務(wù),一般就是爬蟲和數(shù)據(jù)庫操作,文件操作非常頻繁的任務(wù),比如我負(fù)責(zé)開發(fā)的審核系統(tǒng),需要同時(shí)對(duì)mysql和redis有大量的讀寫操作,所以我使用多線程進(jìn)行消費(fèi)。線程使用的是Threading庫 ,往往是先聲明線程實(shí)例,里面可以傳入消費(fèi)方法名稱和不定長(zhǎng)參數(shù)args,然后將實(shí)例放入指定線程數(shù)的容器中(list),通過循環(huán)或者列表推導(dǎo)式,使用start方法開啟線程,join方法阻塞主線程。

協(xié)程

協(xié)程是一種用戶態(tài)的輕量級(jí)線程,協(xié)程的調(diào)度完全由用戶控制,不像進(jìn)程和線程是系統(tǒng)態(tài),所以在不主動(dòng)切換協(xié)程的情況下,操作全局變量的時(shí)候,可以無需加鎖(這里有坑,協(xié)程庫內(nèi)置也是有鎖的,但是看場(chǎng)景,如果使用場(chǎng)景內(nèi)沒有主動(dòng)切換協(xié)程(await)寫操作就不需要加鎖,如果單協(xié)程執(zhí)行過程中,主動(dòng)切換了協(xié)程,寫操作則需要加鎖 協(xié)程是否加鎖問題),只需要判斷資源狀態(tài)即可,效率非常高,同時(shí)協(xié)程是單線程的,即可以共享內(nèi)存,又不需要系統(tǒng)態(tài)的線程切換,同時(shí)也不會(huì)觸發(fā)gil全局解釋器鎖,所以它性能比線程要高。具體使用場(chǎng)景和線程一樣,適合io密集型任務(wù),所謂io密集型任務(wù)就是大量的硬盤讀寫操作或者網(wǎng)絡(luò)tcp通信的任務(wù),一般就是爬蟲和數(shù)據(jù)庫操作,文件操作非常頻繁的任務(wù),比如我負(fù)責(zé)開發(fā)的審核系統(tǒng),需要同時(shí)對(duì)mysql和redis有大量的讀寫操作,所以我后期將多線程改造成協(xié)程進(jìn)行消費(fèi)。協(xié)程我使用的python原生協(xié)程庫asyncio庫,首先通過asyncio.ensure_future(doout(4))方法建立協(xié)程對(duì)象,然后根據(jù)當(dāng)天審核員數(shù)量指定開啟協(xié)程數(shù),和多線程以及多進(jìn)程的區(qū)別是,協(xié)程既可以直接傳實(shí)參,也可以傳不定長(zhǎng)參數(shù),很方便,然后通過await asyncio.gather(*tasks)方法啟動(dòng)協(xié)程,需要注意的是,主方法需要聲明成async方法,并且通過asyncio.run(main())來啟動(dòng)。協(xié)程雖然是python異步編程的最佳方式,但是我認(rèn)為它也有缺點(diǎn),那就是異步寫法導(dǎo)致代碼可讀性下降,同時(shí)對(duì)編程人員的綜合素質(zhì)要求高,并不是所有人都能理解協(xié)程的工作方式,以及python原生協(xié)程的異步寫法。

Python中的深拷貝和淺拷貝

僅次于三程問題的明星面試題,一般情況下,大家都會(huì)說淺拷貝修改復(fù)制對(duì)象會(huì)影響原對(duì)象,而深拷貝不會(huì),但其實(shí),淺拷貝會(huì)有三種細(xì)分的情況:

1.拷貝不可變對(duì)象:只是增加一個(gè)指向原對(duì)象的引用,改變會(huì)互相影響。

>>> a = (1, 2, [3, 4])  
>>> b = copy.copy(a)  
>>> b  
... (1, 2, [3, 4])  
# 改變一方,另一方也改變  
>>> b[2].append(5)  
>>> a  
... (1, 2, [3, 4, 5])

2.拷貝可變對(duì)象(一層結(jié)構(gòu)):產(chǎn)生新的對(duì)象,開辟新的內(nèi)存空間,改變互不影響。

>>> import copy  
  
>>> a = [1, 2, 3]  
>>> b = copy.copy(a)  
>>> b  
... [1, 2, 3]  
# 查看兩者的內(nèi)存地址,不同,開辟了新的內(nèi)存空間  
>>> id(b)  
... 1833997595272  
>>> id(a)  
... 1833997595080  
>>> a is b  
... False  
# 改變了一方,另一方不會(huì)改變  
a = [1, 2, 3]    b = [1, 2, 3]  
>>> b.append(4)  
>>> a  
... [1, 2, 3]  
>>> a.append(5)  
>>> b  
... [1, 2, 3, 4]

3.拷貝可變對(duì)象(多層結(jié)構(gòu)):產(chǎn)生新的對(duì)象,開辟新的內(nèi)存空間,不改變包含的子對(duì)象則互不影響、改變包含的子對(duì)象則互相影響。

>>> import copy  
  
>>> a = [1, 2, [3, 4]]  
>>> b = copy.copy(a)  
>>> b  
... [1, 2, [3, 4]]  
# 查看兩者的內(nèi)存地址,不同,開辟了新的內(nèi)存空間  
>>> id(b)  
1833997596488  
>>> id(a)  
1833997596424  
>>> a is b  
... False  
# 1.沒有對(duì)包含的子對(duì)象進(jìn)行修改,另一方關(guān)我卵事  
a = [1, 2, [3, 4]]    b = [1, 2, [3, 4]]  
>>> b.append(5)  
>>> a  
... [1, 2, [3, 4]]  
>>> a.append(6)  
>>> b  
... [1, 2, [3, 4], 5]  
# 2.對(duì)包含的子對(duì)象進(jìn)行修改,另一方也隨之改變  
a = [1, 2, [3, 4]]    b = [1, 2, [3, 4]]  
>>> b[2].append(5)  
>>> a  
... [1, 2, [3, 4, 5]]  
>>> a[2].append(6)  
>>> b  
... [1, 2, [3, 4, 5, 6]]

高并發(fā)如何進(jìn)行處理的

既然JD(Job Describe)中提到了高并發(fā),那么就一定會(huì)問高并發(fā)問題,一般情況下,涉及高并發(fā)場(chǎng)景的基本上都是外部系統(tǒng),此時(shí)需要簡(jiǎn)單介紹一下系統(tǒng)的容量是多少,比如有注冊(cè)用戶數(shù)、日活、QPS等等。然后就是提供具體方案,一般的手段是加緩存,數(shù)據(jù)庫讀寫分離,數(shù)據(jù)庫 sharding 等等。高并發(fā)背景下,整個(gè)系統(tǒng)瓶頸一般都在數(shù)據(jù)庫。

除了上述的一些常規(guī)方案,業(yè)內(nèi)最常用的緩解高并發(fā)的手段是使用異步任務(wù)隊(duì)列:

為了解決生產(chǎn)者和消費(fèi)者過度耦合的效率低下問題,我設(shè)計(jì)了一個(gè)緩沖區(qū),生產(chǎn)者不會(huì)直接和消費(fèi)者產(chǎn)生關(guān)系,而是通過緩沖區(qū)解耦,這個(gè)緩沖區(qū)就是異步任務(wù)隊(duì)列,隊(duì)列容器我采用redis數(shù)據(jù)庫,因?yàn)閞edis性能優(yōu)勢(shì)比較明顯,同時(shí)內(nèi)置的list數(shù)據(jù)類型比較契合隊(duì)列這種數(shù)據(jù)結(jié)構(gòu),工具類內(nèi)置了,初始化方法,入隊(duì)方法,出隊(duì)方法,隊(duì)列長(zhǎng)度,以及查重唯一方法。每當(dāng)商戶提交表單,此時(shí)并不會(huì)修改狀態(tài),而是將表單數(shù)據(jù)入庫,同時(shí)將商戶uid進(jìn)行入隊(duì)操作,遵循fifo原則,在消費(fèi)者端使用異步的方式進(jìn)行消費(fèi),也就是出隊(duì)操作,每一個(gè)線程對(duì)應(yīng)一個(gè)審核員,通過消費(fèi)方法進(jìn)行傳參,每次將出隊(duì)的商戶uid和線程傳入的審核員id進(jìn)行組合分配,出隊(duì)之后并發(fā)數(shù)已經(jīng)得到了控制,隨后在mysql端進(jìn)行update操作,達(dá)到異步分配審核的目的。

保持冪等性

如果面試中提到了異步任務(wù)隊(duì)列(消息隊(duì)列),那么冪等性操作幾乎一定會(huì)在后續(xù)的問題中提及,所謂冪等性,簡(jiǎn)單來說就是對(duì)于同一個(gè)系統(tǒng),在同樣條件下,一次請(qǐng)求和重復(fù)多次請(qǐng)求對(duì)資源的影響是一致的,就稱該操作為冪等的。比如說如果有一個(gè)接口是冪等的,當(dāng)傳入相同條件時(shí),其效果必須是相同的。在RabbitMQ中消費(fèi)冪等就是指給消費(fèi)者發(fā)送多條同樣的消息,消費(fèi)者只會(huì)消費(fèi)其中的一條。例如,在一次購物中提交訂單進(jìn)行支付時(shí),當(dāng)網(wǎng)絡(luò)延遲等其他問題造成消費(fèi)者重新支付,如果沒有冪等性的支持,那么會(huì)對(duì)同一訂單進(jìn)行兩次扣款,這是非常嚴(yán)重的,因此有了冪等性,當(dāng)對(duì)同一個(gè)訂單進(jìn)行多次支付時(shí),可以確保只對(duì)同一個(gè)訂單扣款一次。

具體手段:

事實(shí)上,當(dāng)審核任務(wù)出隊(duì)之后,如果在消費(fèi)端出現(xiàn)意外,這個(gè)意外包含但不限于出對(duì)后tornado宕機(jī)、mysql宕機(jī)等等,導(dǎo)致出隊(duì)任務(wù)沒有進(jìn)行流程化處理,所以我采用了ack驗(yàn)證機(jī)制,也就是緩沖區(qū)隊(duì)列從單隊(duì)列升級(jí)為雙隊(duì)列,把rpop出隊(duì)改成redis內(nèi)置的rpoplpush的原子性操作,出隊(duì)后立即進(jìn)入確認(rèn)隊(duì)列,在消費(fèi)端完成審核任務(wù)后,對(duì)ack隊(duì)列進(jìn)行確認(rèn)移除操作,如此,一次審批任務(wù)才算完結(jié),如果任務(wù)生命周期內(nèi),任務(wù)一直存在于確認(rèn)隊(duì)列沒有出隊(duì),那么輪詢?nèi)蝿?wù)會(huì)將任務(wù)id移出確認(rèn)隊(duì)列,重新在緩沖區(qū)隊(duì)列進(jìn)行入隊(duì)操作,這樣就避免了,僵審任務(wù)的問題。

結(jié)語

技術(shù)面試雖然是一種信息不對(duì)等的較量,但是只要認(rèn)真研究JD(Job Describe),做好相關(guān)的知識(shí)儲(chǔ)備,基礎(chǔ)常識(shí)不要翻車(包含但不限于Python基礎(chǔ)/數(shù)據(jù)庫基礎(chǔ)),那么作為應(yīng)聘者拿一個(gè)Offer也不是想象中的那么難,本次面試的實(shí)戰(zhàn)錄音可以在B站(Youtube)搜索劉悅的技術(shù)博客查閱,歡迎諸君品鑒。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容