1. 程式人生 > >Python面試題【BAT版】

Python面試題【BAT版】

 

寫在前面

今天給大家分享2018年BAT面試過程中的python的相關問題。所有的面試題和答案均來源於網路,如有侵權請及時聯絡,答案如有不正確還請各位及時指正。為面試而生希望各位都能化身offer收割機。

 

問題清單

Python語言特性


1.Python的函式引數傳遞

看兩個如下例子,分析執行結果:
程式碼一:

1a = 1
2def fun(a):
3    a = 2
4fun(a)
5print(a)  # 1


程式碼二:

1a = []
2def fun(a):
3a.append(1)
4fun(a)
5print(a)  # [1]

    
所有的變數都可以理解是記憶體中一個物件的“引用”,或者,也可以看似c中void*的感覺。這裡記住的是型別是屬於物件的,而不是變數。而物件有兩種,“可更改”(mutable)與“不可更改”(immutable)物件。在python中,strings, tuples, 和numbers是不可更改的物件,而list,dict等則是可以修改的物件。(這就是這個問題的重點)當一個引用傳遞給函式的時候,函式自動複製一份引用,這個函式裡的引用和外邊的引用沒有半毛關係了.所以第一個例子裡函式把引用指向了一個不可變物件,當函式返回的時候,外面的引用沒半毛感覺.而第二個例子就不一樣了,函式內的引用指向的是可變物件,對它的操作就和定位了指標地址一樣,在記憶體裡進行修改。
 

2.Python中的元類(metaclass)

元類就是用來建立類的“東西”。你建立類就是為了建立類的例項物件,但是我們已經學習到了Python中的類也是物件。好吧,元類就是用來建立這些類(物件)的,元類就是類的類。

 

[email protected]和@classmethod

Python其實有3個方法,即靜態方法(staticmethod),類方法(classmethod)和例項方法,如下:

 1class A(object):
 2    def foo(self,x):
 3        print "executing foo(%s,%s)"%(self,x)
 4
 5    @classmethod
 6    def class_foo(cls,x):
 7        print( "executing class_foo(%s,%s)"%(cls,x))
 8
 9    @staticmethod
10    def static_foo(x):
11        print ("executing static_foo(%s)"%x)
12
13a=A()


這裡先理解下函式引數裡面的self和cls.這個self和cls是對類或者例項的繫結.對於例項方法,我們知道在類裡每次定義方法的時候都需要繫結這個例項,就是foo(self, x),為什麼要這麼做呢?因為例項方法的呼叫離不開例項,我們需要把例項自己傳給函式,呼叫的時候是這樣的a.foo(x)(其實是foo(a, x)).類方法一樣,只不過它傳遞的是類而不是例項,A.class_foo(x).注意這裡的self和cls可以替換別的引數,但是python的約定是這倆,還是不要改的好。對於靜態方法其實和普通的方法一樣,不需要對誰進行繫結,唯一的區別是呼叫的時候需要使用a.static_foo(x)或者A.static_foo(x)來呼叫。

 

\

例項方法

類方法

靜態方法

a = A()

a.foo(x)

a.class_foo(x)

a.static_foo(x)

A

不可用

A.class_foo(x)

A.static_foo(x)


4.類變數和例項變數
 

1class Person:
2    name="aaa"
3
4p1=Person()
5p2=Person()
6p1.name="bbb"
7print(p1.name)  # bbb
8print(p2.name)  # aaa
9print(Person.name)  # aaa


類變數就是供類使用的變數,例項變數就是供例項使用的。
這裡p1.name="bbb"是例項呼叫了類變數,這其實和上面第一個問題一樣,就是函式傳參的問題,p1.name一開始是指向的類變數name="aaa",但是在例項的作用域裡把類變數的引用改變了,就變成了一個例項變數,self.name不再引用Person的類變數name了.
可以看看下面的例子:
 

1class Person:
2    name=[]
3
4p1=Person()
5p2=Person()
6p1.name.append(1)
7print(p1.name)  # [1]
8print(p2.name)  # [1]
9print(Person.name)  # [1]

 

5.Python自省

這個也是python彪悍的特性.

自省就是面向物件的語言所寫的程式在執行時,所能知道物件的型別.簡單一句就是執行時能夠獲得物件的型別.比如type(),dir(),getattr(),hasattr(),isinstance().

6.字典推導式

可能你見過列表推導式,卻沒有見過字典推導式,在2.7中才加入的:

1d = {key: value for (key, value) in iterable}

 

7.Python中單下劃線和雙下劃線

 1>>> class MyClass():
 2...     def __init__(self):
 3...             self.__superprivate = "Hello"
 4...             self._semiprivate = ", world!"
 5...
 6>>> mc = MyClass()
 7>>> print(mc.__superprivate)
 8Traceback (most recent call last):
 9  File "<stdin>", line 1, in <module>
10AttributeError: myClass instance has no attribute '__superprivate'
11>>> print(mc._semiprivate)
12, world!
13>>> print mc.__dict__
14{'_MyClass__superprivate': 'Hello', '_semiprivate': ', world!'}


__foo__:一種約定,Python內部的名字,用來區別其他使用者自定義的命名,以防衝突.
_foo:一種約定,用來指定變數私有.程式設計師用來指定私有變數的一種方式.
__foo:這個有真正的意義:解析器用_classname__foo來代替這個名字,以區別和其他類相同的命名.
詳情見:
http://www.zhihu.com/question/19754941

8.字串格式化:%和.format
.format在許多方面看起來更便利.對於%最煩人的是它無法同時傳遞一個變數和元組.你可能會想下面的程式碼不會有什麼問題:
Python:

1"hi there %s" % name


但是,如果name恰好是(1,2,3),它將會丟擲一個TypeError異常.為了保證它總是正確的,你必須這樣做:

1hi there %s" % (name,)   # 提供一個單元素的陣列而不是一個引數
2


9.迭代器和生成器
在Python中,這種一邊迴圈一邊計算的機制,稱為生成器:generator。
可以被next()函式呼叫並不斷返回下一個值的物件稱為迭代器:Iterator。

這個是stackoverflow裡python排名第一的問題,值得一看: http://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do-in-python

10.*args and **kwargs
用*args和**kwargs只是為了方便並沒有強制使用它們。
當你不確定你的函式裡將要傳遞多少引數時你可以用*args.例如,它可以傳遞任意數量的引數:

1>>> def print_everything(*args):
2        for count, thing in enumerate(args):
3...         print '{0}. {1}'.format(count, thing)
4...
5>>> print_everything('apple', 'banana', 'cabbage')
60. apple
71. banana
82. cabbage


相似的,**kwargs允許你使用沒有事先定義的引數名:

1>>> def table_things(**kwargs):
2...     for name, value in kwargs.items():
3...         print '{0} = {1}'.format(name, value)
4...
5>>> table_things(apple = 'fruit', cabbage = 'vegetable')
6cabbage = vegetable
7apple = fruit


你也可以混著用.命名引數首先獲得引數值然後所有的其他引數都傳遞給*args和**kwargs.命名引數在列表的最前端.例如:

1def table_things(titlestring, **kwargs)
2

*args和**kwargs可以同時在函式的定義中,但是*args必須在**kwargs前面.當呼叫函式時你也可以用*和**語法.例如:

1>>> def print_three_things(a, b, c):
2...     print 'a = {0}, b = {1}, c = {2}'.format(a,b,c)
3...
4>>> mylist = ['aardvark', 'baboon', 'cat']
5>>> print_three_things(*mylist)
6
7a = aardvark, b = baboon, c = cat


就像你看到的一樣,它可以傳遞列表(或者元組)的每一項並把它們解包.注意必須與它們在函式裡的引數相吻合.當然,你也可以在函式定義或者函式呼叫時用*.
http://stackoverflow.com/questions/3394835/args-and-kwargs

11.面向切面程式設計AOP和裝飾器
這個AOP一聽起來有點懵,同學面試的時候就被問懵了…
裝飾器是一個很著名的設計模式,經常被用於有切面需求的場景,較為經典的有插入日誌、效能測試、事務處理等。裝飾器是解決這類問題的絕佳設計,有了裝飾器,我們就可以抽離出大量函式中與函式功能本身無關的雷同程式碼並繼續重用。概括的講,裝飾器的作用就是為已經存在的物件新增額外的功能。
這個問題比較大,推薦:
http://stackoverflow.com/questions/739654/how-can-i-make-a-chain-of-function-decorators-in-python
中文:
http://taizilongxu.gitbooks.io/stackoverflow-about-python/content/3/README.html

12.鴨子型別
“當看到一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那麼這隻鳥就可以被稱為鴨子。”
我們並不關心物件是什麼型別,到底是不是鴨子,只關心行為。
比如在python中,有很多file-like的東西,比如StringIO,GzipFile,socket。它們有很多相同的方法,我們把它們當作檔案使用。
又比如list.extend()方法中,我們並不關心它的引數是不是list,只要它是可迭代的,所以它的引數可以是list/tuple/dict/字串/生成器等.
鴨子型別在動態語言中經常使用,非常靈活,使得python不想java那樣專門去弄一大堆的設計模式。

13.Python中過載

引自知乎:
http://www.zhihu.com/question/20053359

函式過載主要是為了解決兩個問題。
1.可變引數型別。
2.可變引數個數。
另外,一個基本的設計原則是,僅僅當兩個函式除了引數型別和引數個數不同以外,其功能是完全相同的,此時才使用函式過載,如果兩個函式的功能其實不同,那麼不應當使用過載,而應當使用一個名字不同的函式。
好吧,那麼對於情況 1 ,函式功能相同,但是引數型別不同,python 如何處理?答案是根本不需要處理,因為 python 可以接受任何型別的引數,如果函式的功能相同,那麼不同的引數型別在 python 中很可能是相同的程式碼,沒有必要做成兩個不同函式。
那麼對於情況 2 ,函式功能相同,但引數個數不同,python 如何處理?大家知道,答案就是預設引數。對那些缺少的引數設定為預設引數即可解決問題。因為你假設函式功能相同,那麼那些缺少的引數終歸是需要用的。
好了,鑑於情況 1 跟 情況 2 都有了解決方案,python 自然就不需要函式過載了。

14.新式類和舊式類

這篇文章很好的介紹了新式類的特性:
http://www.cnblogs.com/btchenguang/archive/2012/09/17/2689146.html
新式類很早在2.2就出現了,所以舊式類完全是相容的問題,Python3裡的類全部都是新式類.這裡有一個MRO問題可以瞭解下(新式類是廣度優先,舊式類是深度優先),<Python核心程式設計>裡講的也很多.

15.__new__和__init__的區別

這個__new__確實很少見到,先做了解吧.
1.__new__是一個靜態方法,而__init__是一個例項方法.
2.__new__方法會返回一個建立的例項,而__init__什麼都不返回.
3.只有在__new__返回一個cls的例項時後面的__init__才能被呼叫.
4.當建立一個新例項時呼叫__new__,初始化一個例項時用__init__.
ps: __metaclass__是建立類時起作用.所以我們可以分別使用__metaclass__,__new__和__init__來分別在類建立,例項建立和例項初始化的時候做一些小手腳.

16.單例模式
這個絕對常考啊.絕對要記住1~2個方法。

1.使用__new__方法

1class Singleton(object):
2    def __new__(cls, *args, **kw):
3        if not hasattr(cls, '_instance'):
4            orig = super(Singleton, cls)
5            cls._instance = orig.__new__(cls, *args, **kw)
6        return cls._instance
7
8class MyClass(Singleton):
9    a = 1


2.共享屬性
建立例項時把所有例項的__dict__指向同一個字典,這樣它們具有相同的屬性和方法。

1class Borg(object):
2    _state = {}
3    def __new__(cls, *args, **kw):
4        ob = super(Borg, cls).__new__(cls, *args, **kw)
5        ob.__dict__ = cls._state
6        return ob
7
8class MyClass2(Borg):
9    a = 1

 

3.裝飾器版本

 1def singleton(cls, *args, **kw):
 2    instances = {}
 3    def getinstance():
 4        if cls not in instances:
 5            instances[cls] = cls(*args, **kw)
 6        return instances[cls]
 7    return getinstance
 8
 [email protected]
10class MyClass:

 

4.import方法
作為python的模組是天然的單例模式

 1class My_Singleton(object):
 2    def foo(self):
 3        pass
 4
 5my_singleton = My_Singleton()
 6
 7# to use
 8from mysingleton import my_singleton
 9
10my_singleton.foo()

 

17.Python中的作用域

Python 中,一個變數的作用域總是由在程式碼中被賦值的地方所決定的。
當 Python 遇到一個變數的話他會按照這樣的順序進行搜尋:
本地作用域(Local)→當前作用域被嵌入的本地作用域(Enclosing locals)→全域性/模組作用域(Global)→內建作用域(Built-in)

 

18.GIL執行緒全域性鎖
執行緒全域性鎖(Global Interpreter Lock),即Python為了保證執行緒安全而採取的獨立執行緒執行的限制,說白了就是一個核只能在同一時間執行一個執行緒.
解決辦法就是多程序和下面的協程(協程也只是單CPU,但是能減小切換代價提升效能).

 

19.協程
簡單點說協程是程序和執行緒的升級版,程序和執行緒都面臨著核心態和使用者態的切換問題而耗費許多切換時間,而協程就是使用者自己控制切換的時機,不再需要陷入系統的核心態.
Python裡最常見的yield就是協程的思想!可以檢視第九個問題.

 

20.閉包
閉包(closure)是函數語言程式設計的重要的語法結構。閉包也是一種組織程式碼的結構,它同樣提高了程式碼的可重複使用性。
當一個內嵌函式引用其外部作作用域的變數,我們就會得到一個閉包. 總結一下,建立一個閉包必須滿足以下幾點:
1.必須有一個內嵌函式
2.內嵌函式必須引用外部函式中的變數
3.外部函式的返回值必須是內嵌函式
感覺閉包還是有難度的,幾句話是說不明白的,還是查查相關資料.
重點是函式執行後並不會被撤銷,就像16題的instance字典一樣,當函式執行完後,instance並不被銷燬,而是繼續留在記憶體空間裡.這個功能類似類裡的類變數,只不過遷移到了函式上.
閉包就像個空心球一樣,你知道外面和裡面,但你不知道中間是什麼樣.

 

21.Python函數語言程式設計
這個需要適當的瞭解一下吧,畢竟函數語言程式設計在Python中也做了引用.
python中函數語言程式設計支援:
filter 函式的功能相當於過濾器。呼叫一個布林函式bool_func來迭代遍歷每個seq中的元素;返回一個使bool_seq返回值為true的元素的序列。

1>>>a = [1,2,3,4,5,6,7]
2>>>b = filter(lambda x: x > 5, a)
3>>>print b
4>>>[6,7]

 

map函式是對一個序列的每個項依次執行函式,下面是對一個序列每個項都乘以2:

1>>> a = map(lambda x:x*2,[1,2,3])
2>>> list(a)
3[2, 4, 6]

 

reduce函式是對一個序列的每個項迭代呼叫函式,下面是求3的階乘:

1>>> reduce(lambda x,y:x*y,range(1,4))
26

 

22.Python裡的拷貝

引用和copy(),deepcopy()的區別

 1import copy
 2a = [1, 2, 3, 4, ['a', 'b']]  #原始物件
 3
 4b = a  #賦值,傳物件的引用
 5c = copy.copy(a)  #物件拷貝,淺拷貝
 6d = copy.deepcopy(a)  #物件拷貝,深拷貝
 7
 8a.append(5)  #修改物件a
 9a[4].append('c')  #修改物件a中的['a', 'b']陣列物件
10
11print 'a = ', a
12print 'b = ', b
13print 'c = ', c
14print 'd = ', d
15
16輸出結果:
17a =  [1, 2, 3, 4, ['a', 'b', 'c'], 5]
18b =  [1, 2, 3, 4, ['a', 'b', 'c'], 5]
19c =  [1, 2, 3, 4, ['a', 'b', 'c']]
20d =  [1, 2, 3, 4, ['a', 'b']]

 

23.read,readline和readlines

read 讀取整個檔案
readline 讀取下一行,使用生成器方法
readlines 讀取整個檔案到一個迭代器以供我們遍歷

 

24.到底什麼是Python?你可以在回答中與其他技術進行對比

下面是一些關鍵點:
Python是一種解釋型語言。這就是說,與C語言和C的衍生語言不同,Python程式碼在執行之前不需要編譯。其他解釋型語言還包括PHP和Ruby。Python是動態型別語言,指的是你在宣告變數時,不需要說明變數的型別。你可以直接編寫類似x=111和x="I'm a string"這樣的程式碼,程式不會報錯。

 

Python非常適合面向物件的程式設計(OOP),因為它支援通過組合(composition)與繼承(inheritance)的方式定義類(class)。Python中沒有訪問說明符(access specifier,類似C++中的public和private),這麼設計的依據是“大家都是成年人了”。
在Python語言中,函式是第一類物件(first-class objects)。這指的是它們可以被指定給變數,函式既能返回函式型別,也可以接受函式作為輸入。類(class)也是第一類物件。

 

Python程式碼編寫快,但是執行速度比編譯語言通常要慢。好在Python允許加入基於C語言編寫的擴充套件,因此我們能夠優化程式碼,消除瓶頸,這點通常是可以實現的。numpy就是一個很好地例子,它的執行速度真的非常快,因為很多算術運算其實並不是通過Python實現的。

 

Python用途非常廣泛——網路應用,自動化,科學建模,大資料應用,等等。它也常被用作“膠水語言”,幫助其他語言和元件改善執行狀況。
Python讓困難的事情變得容易,因此程式設計師可以專注於演算法和資料結構的設計,而不用處理底層的細節。

 

為什麼提這個問題:
如果你應聘的是一個Python開發崗位,你就應該知道這是門什麼樣的語言,以及它為什麼這麼酷。以及它哪裡不好。

 

25.閱讀下面的程式碼,寫出A0,A1至An的最終值。

 

 1A0 = dict(zip(('a','b','c','d','e'),(1,2,3,4,5)))
 2A1 = range(10)
 3A2 = [i for i in A1 if i in A0]
 4A3 = [A0[s] for s in A0]
 5A4 = [i for i in A1 if i in A3]
 6A5 = {i:i*i for i in A1}
 7A6 = [[i,i*i] for i in A1]

 8答案
 9A0 = {'a': 1, 'c': 3, 'b': 2, 'e': 5, 'd': 4}
10A1 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
11A2 = []
12A3 = [1, 3, 2, 5, 4]
13A4 = [1, 2, 3, 4, 5]
14A5 = {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}
15A6 = [[0, 0], [1, 1], [2, 4], [3, 9], [4, 16], [5, 25], [6, 36], [7, 49], [8, 64], [9, 81]]

 

為什麼提這個問題:
列表解析(list comprehension)十分節約時間,對很多人來說也是一個大的學習障礙。如果你讀懂了這些程式碼,就很可能可以寫下正確地值。其中部分程式碼故意寫的怪怪的。因為你共事的人之中也會有怪人。

 

26.介紹一下except的用法和作用?

try…except…except…[else…][finally…]
執行try下的語句,如果引發異常,則執行過程會跳到except語句。對每個except分支順序嘗試執行,如果引發的異常與except中的異常組匹配,執行相應的語句。如果所有的except都不匹配,則異常會傳遞到下一個呼叫本程式碼的最高層try程式碼中。
try下的語句正常執行,則執行else塊程式碼。如果發生異常,就不會執行
如果存在finally語句,最後總是會執行。

 

27.如何用Python來進行查詢和替換一個文字字串?

答:可以使用re模組中的sub()函式或者subn()函式來進行查詢和替換,
格式:

sub(replacement, string[,count=0])

(replacement)是被替換成的文字,string是需要被替換的文字,count是一個可選引數,指最大被替換的數量)

 

1>>> import re
2>>>p=re.compile(‘blue|white|red’)
3>>>print(p.sub(‘colour’,'blue socks and red shoes’))
4colour socks and colourshoes
5>>>print(p.sub(‘colour’,'blue socks and red shoes’,count=1))
6colour socks and redshoes

7subn()方法執行的效果跟sub()一樣,不過它會返回一個二維陣列,包括替換後的新的字串和總共替換的數量

 

28.用Python匹配HTML tag的時候,<.*>和<.*?>有什麼區別?

 

答:術語叫貪婪匹配( <.*> )和非貪婪匹配(<.*?> )
例如:
test
<.*> :
test
<.*?> :

 

29.有沒有一個工具可以幫助查詢python的bug和進行靜態的程式碼分析?

答:PyChecker是一個python程式碼的靜態分析工具,它可以幫助查詢python程式碼的bug, 會對程式碼的複雜度和格式提出警告,Pylint是另外一個工具可以進行codingstandard檢查。
 

30.Python和多執行緒(multi-threading)。這是個好主意碼?列舉一些讓Python程式碼以並行方式執行的方法。

Python並不支援真正意義上的多執行緒。Python中提供了多執行緒包,但是如果你想通過多執行緒提高程式碼的速度,使用多執行緒包並不是個好主意。Python中有一個被稱為Global Interpreter Lock(GIL)的東西,它會確保任何時候你的多個執行緒中,只有一個被執行。執行緒的執行速度非常之快,會讓你誤以為執行緒是並行執行的,但是實際上都是輪流執行。經過GIL這一道關卡處理,會增加執行的開銷。這意味著,如果你想提高程式碼的執行速度,使用threading包並不是一個很好的方法。
不過還是有很多理由促使我們使用threading包的。如果你想同時執行一些任務,而且不考慮效率問題,那麼使用這個包是完全沒問題的,而且也很方便。但是大部分情況下,並不是這麼一回事,你會希望把多執行緒的部分外包給作業系統完成(通過開啟多個程序),或者是某些呼叫你的Python程式碼的外部程式(例如Spark或Hadoop),又或者是你的Python程式碼呼叫的其他程式碼(例如,你可以在Python中呼叫C函式,用於處理開銷較大的多執行緒工作)。

 

為什麼提這個問題
因為GIL就是個混賬東西(A-hole)。很多人花費大量的時間,試圖尋找自己多執行緒程式碼中的瓶頸,直到他們明白GIL的存在。

文章首發自公眾號【Ahab雜貨鋪】關注公眾號技術分享第一時間送達!