溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Python面試寶典之基礎(chǔ)篇-02

發(fā)布時間:2020-08-09 06:41:50 來源:ITPUB博客 閱讀:195 作者:千鋒Python唐小強(qiáng) 欄目:編程語言

我覺得你如果正在找工作,我的Python面試寶典幾期教程,你一定得花時間看完了!

Python面試寶典之基礎(chǔ)篇-02

題目006:說說Python中的淺拷貝和深拷貝。

點(diǎn)評:這個題目本身出現(xiàn)的頻率非常高,但是就題論題而言沒有什么技術(shù)含量。對于這種面試題,在 回答的時候一定要讓你的答案能夠超出面試官的預(yù)期,這樣才能 獲得更好的印象分。所以回答這個題目的要點(diǎn)不僅僅是能夠說出淺拷貝和深拷貝的區(qū)別,深拷貝的時候可能遇到的兩大問題,還要說出Python標(biāo)準(zhǔn)庫對淺拷貝和深拷貝的支持,然后可以說說列表、字典如何實(shí)現(xiàn)拷貝操作以及如何通過序列化和反序列的方式實(shí)現(xiàn)深拷貝,最后還可以提到設(shè)計模式中的原型模式以及它在項(xiàng)目中的應(yīng)用。

淺拷貝通常只復(fù)制對象本身,而深拷貝不僅會復(fù)制對象,還會遞歸的復(fù)制對象所關(guān)聯(lián)的對象。深拷貝可能會遇到兩個問題:一是一個對象如果直接或間接的引用了自身,會導(dǎo)致無休止的遞歸拷貝;二是深拷貝可能對原本設(shè)計為多個對象共享的數(shù)據(jù)也進(jìn)行拷貝。Python通過copy模塊中的copy和deepcopy函數(shù)來實(shí)現(xiàn)淺拷貝和深拷貝操作,其中deepcopy可以通過memo字典來保存已經(jīng)拷貝過的對象,從而避免剛才所說的自引用遞歸問題;此外,可以通過copyreg模塊的pickle函數(shù)來定制指定類型對象的拷貝行為。

deepcopy函數(shù)的本質(zhì)其實(shí)就是對象的一次序列化和一次返回序列化,面試題中還考過用自定義函數(shù)實(shí)現(xiàn)對象的深拷貝操作,顯然我們可以使用pickle模塊的dumps和loads來做到,代碼如下所示。


import pickle


my_deep_copy = lambda obj: pickle.loads(pickle.dumps(obj))

列表的切片操作[:]相當(dāng)于實(shí)現(xiàn)了列表對象的淺拷貝,而字典的copy方法可以實(shí)現(xiàn)字典對象的淺拷貝。對象拷貝其實(shí)是更為快捷的創(chuàng)建對象的方式。在Python中,通過構(gòu)造器創(chuàng)建對象屬于兩階段構(gòu)造,首先是分配內(nèi)存空間,然后是初始化。在創(chuàng)建對象時,我們也可以基于“原型”的對象來創(chuàng)建新對象,通過對原型對象的拷貝(復(fù)制內(nèi)存)就完成了對象的創(chuàng)建和初始化,這種做法其實(shí)更加高效,這也就是設(shè)計模式中的原型模式。我們可以通過元類的方式來實(shí)現(xiàn)原型模式,代碼如下所示。


import copy
class PrototypeMeta(type):

    """實(shí)現(xiàn)原型模式的元類"""

    def __init__ (cls, *args, **kwargs):
       super().__init__(*args, **kwargs)
        # 為對象綁定clone方法來實(shí)現(xiàn)對象拷貝
       cls.clone = lambda self, is_deep=True: \
           copy.deepcopy(self) if is_deep else copy.copy(self) class Person(metaclass=PrototypeMeta):
   pass p1 = Person()
p2 = p1.clone()                 # 深拷貝
p3 = p1.clone(is_deep=False)    # 淺拷貝

題目007:Python是如何實(shí)現(xiàn)內(nèi)存管理的?

點(diǎn)評:當(dāng)面試官問到這個問題的時候,一個展示自己的機(jī)會就擺在面前了。你要先反問面試官:“你說的是官方的CPython解釋器嗎?”。這個反問可以展示出你了解過Python解釋器的不同的實(shí)現(xiàn)版本,而且你也知道面試官想問的是CPython。當(dāng)然,很多面試官對不同的Python解釋器底層實(shí)現(xiàn)到底有什么差別也沒有概念。所以, 千萬不要覺得面試官一定比你強(qiáng),懷揣著這份自信可以讓你更好的完成面試。

Python提供了自動化的內(nèi)存管理,也就是說內(nèi)存空間的分配與釋放都是由Python解釋器在運(yùn)行時自動進(jìn)行的,自動管理內(nèi)存功能極大的減輕程序員的工作負(fù)擔(dān),也能夠幫助程序員在一定程度上解決內(nèi)存泄露的問題。以CPython解釋器為例,它的內(nèi)存管理有三個關(guān)鍵點(diǎn):引用計數(shù)、標(biāo)記清理、分代收集。

引用計數(shù):對于CPython解釋器來說,Python中的每一個對象其實(shí)就是PyObject結(jié)構(gòu)體,它的內(nèi)部有一個名為ob_refcnt 的引用計數(shù)器成員變量。程序在運(yùn)行的過程中ob_refcnt的值會被更新并藉此來反映引用有多少個變量引用到該對象。當(dāng)對象的引用計數(shù)值為0時,它的內(nèi)存就會被釋放掉。


typedef 

struct _
object {

   _PyObject_HEAD_EXTRA
   Py_ssize_t ob_refcnt;
    struct _ typeobject * ob_type;
} PyObject;

以下情況會導(dǎo)致引用計數(shù)加1:

  • 對象被創(chuàng)建
  • 對象被引用
  • 對象作為參數(shù)傳入到一個函數(shù)中
  • 對象作為元素存儲到一個容器中

以下情況會導(dǎo)致引用計數(shù)減1:

  • 用del語句顯示刪除對象引用
  • 對象引用被重新賦值其他對象
  • 一個對象離開它所在的作用域
  • 持有該對象的容器自身被銷毀
  • 持有該對象的容器刪除該對象

可以通過sys模塊的getrefcount函數(shù)來獲得對象的引用計數(shù)。引用計數(shù)的內(nèi)存管理方式在遇到循環(huán)引用的時候就會出現(xiàn)致命傷,因此需要其他的垃圾回收算法對其進(jìn)行補(bǔ)充。

標(biāo)記清理 :CPython使用了“標(biāo)記-清理”(Mark and Sweep)算法解決容器類型可能產(chǎn)生的循環(huán)引用問題。該算法在垃圾回收時分為兩個階段:標(biāo)記階段,遍歷所有的對象,如果對象是可達(dá)的(被其他對象引用),那么就標(biāo)記該對象為可達(dá);清除階段,再次遍歷對象,如果發(fā)現(xiàn)某個對象沒有標(biāo)記為可達(dá),則就將其回收。CPython底層維護(hù)了兩個雙端鏈表,一個鏈表存放著需要被掃描的容器對象(姑且稱之為鏈表A),另一個鏈表存放著臨時不可達(dá)對象(姑且稱之為鏈表B)。為了實(shí)現(xiàn)“標(biāo)記-清理”算法,鏈表中的每個節(jié)點(diǎn)除了有記錄當(dāng)前引用計數(shù)的ref_count變量外,還有一個gc_ref變量,這個gc_ref是ref_count的一個副本,所以初始值為ref_count的大小。執(zhí)行垃圾回收時,首先遍歷鏈表A中的節(jié)點(diǎn),并且將當(dāng)前對象所引用的所有對象的gc_ref減1,這一步主要作用是解除循環(huán)引用對引用計數(shù)的影響。再次遍歷鏈表A中的節(jié)點(diǎn),如果節(jié)點(diǎn)的gc_ref值為0,那么這個對象就被標(biāo)記為“暫時不可達(dá)” (
GC_TENTATIVELY_UNREACHABLE) 并被移動到鏈表B中;如果節(jié)點(diǎn)的gc_ref不為0,那么這個對象就會被標(biāo)記為“可達(dá)“ (GC_REACHABLE),對于”可達(dá)“對象,還要遞歸的將該節(jié)點(diǎn)可以到達(dá)的節(jié)點(diǎn)標(biāo)記為”可達(dá)“;鏈表B中被標(biāo)記為”可達(dá)“的節(jié)點(diǎn)要重新放回到鏈表A中。在兩次遍歷之后,鏈表B中的節(jié)點(diǎn)就是需要釋放內(nèi)存的節(jié)點(diǎn)。

分代回收:在循環(huán)引用對象的回收中,整個應(yīng)用程序會被暫停,為了減少應(yīng)用程序暫停的時間,Python 通過分代回收(空間換時間)的方法提高垃圾回收效率。分代回收的基本思想是: 對象存在的時間越長,是垃圾的可能性就越小,應(yīng)該盡量不對這樣的對象進(jìn)行垃圾回收。CPython將對象分為三種世代分別記為0、1、2,每一個新生對象都在第0代中,如果該對象在一輪垃圾回收掃描中存活下來,那么它將被移到第1代中,存在于第1代的對象將較少的被垃圾回收掃描到;如果在對第1代進(jìn)行垃圾回收掃描時,這個對象又存活下來,那么它將被移至第2代中,在那里它被垃圾回收掃描的次數(shù)將會更少。分代回收掃描的門限值可以通過gc模塊的get_threshold函數(shù)來獲得,該函數(shù)返回一個三元組,分別表示多少次內(nèi)存分配操作后會執(zhí)行0代垃圾回收,多少次0代垃圾回收后會執(zhí)行1代垃圾回收,多少次1代垃圾回收后會執(zhí)行2代垃圾回收。需要說明的是,如果執(zhí)行一次2代垃圾回收,那么比它年輕的代都要執(zhí)行垃圾回收。如果想修改這幾個門限值,可以通過gc模塊的set_threshold函數(shù)來做到。

題目008:說一下你對Python中迭代器和生成器的理解。

點(diǎn)評:很多人面試者都會寫迭代器和生成器,但是卻無法準(zhǔn)確的解釋什么是迭代器和生成器。如果你也有同樣的困惑,可以參考下面的回答。

迭代器是實(shí)現(xiàn)了迭代器協(xié)議的對象。跟其他編程語言不通,Python中沒有用于定義協(xié)議或表示約定的關(guān)鍵字,像interface、protocol這些單詞并不在Python語言的關(guān)鍵字列表中。Python語言通過魔法方法來表示約定,也就是我們所說的協(xié)議,而__next__和__iter__這兩個魔法方法就代表了迭代器協(xié)議。生成器是迭代器的語法升級版本,可以用更為簡單的代碼來實(shí)現(xiàn)一個迭代器。

面試中經(jīng)常會讓面試者寫生成斐波那契數(shù)列的迭代器,下面給出參考代碼,其他的迭代器可以如法炮制。


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()

如果用生成器的語法來改寫上面的代碼,代碼會簡單優(yōu)雅很多。



def 
fib
(num):

   a, b = 0, 1
    for _ in range(num):
       a, b = b, a + b
        yield a

可以通過for-in循環(huán)從迭代器對象中取出值,也可以使用next函數(shù)取出迭代器對象中的下一個值。

題目009:正則表達(dá)式的match方法和search方法有什么區(qū)別?

點(diǎn)評:正則表達(dá)式是字符串處理的重要工具,所以也是面試中經(jīng)常考察的知識點(diǎn)。在Python中,使用正則表達(dá)式有兩種方式,一種是直接調(diào)用re模塊中的函數(shù),傳入正則表達(dá)式和需要處理的字符串;一種是先通過re模塊的compile函數(shù)創(chuàng)建正則表達(dá)式對象,然后再通過對象調(diào)用方法并傳入需要處理的字符串。如果一個正則表達(dá)式被頻繁的使用,我們推薦后面這種方式,它會減少頻繁編譯同一個正則表達(dá)式所造成的開銷。

match方法是從字符串的起始位置進(jìn)行正則表達(dá)式匹配,返回Match對象或None。search方法會掃描整個字符串來找尋匹配的模式,同樣也是返回Match對象或None。

題目010:下面這段代碼的執(zhí)行結(jié)果是什么。



def 
multiply
():

    return [ lambda x: i * x for i in range( 4)]

print([m( 100) for m in multiply()])

運(yùn)行結(jié)果:

[300, 300, 300, 300]

上面代碼的運(yùn)行結(jié)果很容易被誤判為[0, 100, 200, 300]。首先需要注意的是multiply函數(shù)用生成式語法返回了一個列表,列表中保存了4個Lambda函數(shù),這4個Lambda函數(shù)會返回傳入的參數(shù)乘以i的結(jié)果。需要注意的是這里有閉包(closure)現(xiàn)象,multiply函數(shù)中的局部變量i的生命周期被延展了,由于i最終的值是3,所以通過m(100)調(diào)列表中的Lambda函數(shù)時會返回300,而且4個調(diào)用都是如此。

如果想得到[0, 100, 200, 300]這個結(jié)果,可以按照下面幾種方式來修改multiply函數(shù)。

方法一:使用生成器,讓函數(shù)獲得i的當(dāng)前值。



def 
multiply
():

    return ( lambda x: i * x for i in range( 4))

print([m( 100) for m in multiply()])

或者



def 
multiply
():

    for i in range( 4):
        yield lambda x: x * i

print([m( 100) for m in multiply()])

方法二:使用偏函數(shù),徹底避開閉包現(xiàn)象。


from functools 
import partial

from operator import __mul__

def multiply():
    return [partial(__mul__, i) for i in range( 4)]

print([m( 100) for m in multiply()])

溫馨提示:Python面試寶典會持續(xù)更新,從基礎(chǔ)到項(xiàng)目實(shí)戰(zhàn)的內(nèi)容都會慢慢覆蓋到。雖然每天只更新5個題目,但是每道題擴(kuò)散出的信息量還是比較大的,希望對找工作的小伙伴所有幫助。

感謝大家一直以來的支持! 有正在學(xué)習(xí)Python的伙伴,或者準(zhǔn)備轉(zhuǎn)行學(xué)習(xí)Python的,或者你是想打牢自己Python基礎(chǔ)的朋友,給你們整理的視頻教程也上架了!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI