1. 程式人生 > 實用技巧 >python面試題總結

python面試題總結

Python語言特性

1. Python的函式引數傳遞

​ 看兩個如下例子,分析執行結果

#程式碼1
a = 1
def fun(a):
    a = 2
fun(a)
print(a)   #1
#程式碼2
a = []
def fun(a):
    a.append(1)
fun(a)
print(a) #[1]

所有的變數都可以理解為記憶體中的一個物件的引用,或者也可以看似C中void*的感覺。

這裡記住的是型別是屬於物件的,而不是變數的。而物件有兩種,可更改(mutable)與不可更改(immutable)物件。在python中,strtupleint是不可更改的物件,而list

dict等則是可以修改的物件。

當一個引用傳遞給函式的時候,函式自動複製一份引用,這個函式的引用和外邊的引用沒有半毛錢關係,所以第一個例子裡函式把引用指向一個不可更改的物件,當函式返回的時候,外面的引用沒感覺,而第二個例子就不一樣,函式內部的引用指向的是可變物件,對它的操作就和定位了指標地址一樣,在記憶體裡進行修改。

2.Python中的元類【metaclass

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

3.靜態方法@staticmethod和類方法@classmethod

Python其實有3個方法,分別是靜態方法、類方法、例項方法。

class A(object):
    def foo(self,x):
        print("saada%s"%(self,x))
 	@classmethod
    def class_foo(cls,x):
        print("asdsad%s"%(cls,x))
    @staticmethod
    def static_foo(x):
        print("adad%s"%(x))
a = A()

先理解函式引數裡面的self、cls 這個self和cls是類或者例項的繫結,對於例項方法,我們知道在類裡每次定義方法的時候都需要繫結這個例項,就是foo(self,x),為什麼要這麼做呢?因為例項方法的呼叫離不開例項,我們需要把例項自己傳給函式。呼叫的時候是這樣的a.foo(x)其實foo(a,x)類方法一樣,只不過它的傳遞是類而不是例項A.class_foo(x),注意這裡的self和cls可以替換別的引數。

對於靜態方法其實和普通方法一樣,不需要對誰進行繫結,唯一的區別是呼叫的時候使用a.statis_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.類變數和例項變數

class Person:
    name = 'aaa'
p1 = Person()
p2 = Person()
p1.name = 'bbb'
print(p1.name)#bbb
print(p2.name)#aaa
print(Person_name)#aaa

類變數就是供類使用的變數

例項變數就是供例項使用的變數

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

#例子
class Persson:
    name = []
p1 = Person()
p2 = Person()
p1.name.append(1)
print(p1.name)#[1]
print(p2.name)#[1]
print(Pseron.name)#[1]

5.Python自省

這屬於python彪悍的特性。

自省就是面向物件的語言所寫的程式在執行時,所能知道物件的型別,簡單一句就是執行的時候能夠知道物件的型別,比如:typedirgetattrhasattrisinstance

6.字典推導式

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

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

class MyClass():
    def __init__(self):
        self.__superprivate = 'Hello'
        self._semiprivate = 'world'
 
mc = MyClass()
print(mc.__superprivate)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: myClass instance has no attribute '__superprivate'
print(mc._semiprivate)
world
print(mc.__dict__)
{'_MyClass__superprivate': 'Hello', '_semiprivate': ', world!'}

__foo__是一種約定,python內部的名字,用來區別其他使用者自定義的命名,防止衝突。

_fo是一種約定,用來指定變數私有。

__foo是解析器用_classname__foo來代替這個名字,以區別和其他類相同的命名。

8.字串格式化%s和format

format在許多方面更便利,對於%s更煩人的是它無法同時傳遞一個變數和元組。

"hi %s" % name

如果name是(1,2,3),它將會丟擲TypeError異常,為了保證它總是正確的,你必須這麼做

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

9.迭代器和生成器

在Python中,一邊迴圈中一邊計算的機制,稱為生成器generator

可以被next函式呼叫並不斷返回下一個值的物件稱為迭代器iterator

10.*args和**args

當不確定函式將要傳遞多少個引數的時候可以用*args。

def print_1(*args):
    for count,thing in enumerate(*args):
        print("{0}{1}".format(count,thing))

**args允許你使用沒有事先定義的引數名

def print_1(**kwargs):
    for name,value in kwargs.items():
        print("{0}{1}".format(name,value))

11.面向切面程式設計AOP和裝飾器

裝飾器是一種設計模式

經常用於切面需求的應用場景

較為經典的有

  • 插入日誌
  • 效能測試
  • 事務處理

裝飾器是解決這類問題的絕佳設計。

有了裝飾器,我們就可以抽離出大量函式中與函式功能本身無關的雷同程式碼並繼續重用。

裝飾器的作用就是為已經存在的物件新增額外的功能。

12 鴨子型別

“當看到一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那麼這隻鳥就可以被稱為鴨子。”

我們並不關心物件是什麼型別,到底是不是鴨子,只關心行為。

比如在python中,有很多file-like的東西,比如StringIO,GzipFile,socket。它們有很多相同的方法,我們把它們當作檔案使用。

又比如list.extend()方法中,我們並不關心它的引數是不是list,只要它是可迭代的,所以它的引數可以是list/tuple/dict/字串/生成器等.

鴨子型別在動態語言中經常使用,非常靈活,使得python不想java那樣專門去弄一大堆的設計模式。

13.過載

函式過載主要是為了解決兩個問題

  • 可變引數型別
  • 可變引數個數

一個基本的設計原則是:僅僅當兩個函式除了引數型別和引數個數不同以外。其功能完全相同的,此時才使用函式常在。

如果兩個函式的功能不同,不應當使用過載,而應該使用一個名字不同的函式。

問題

  • 函式功能相同,但引數型別不同,python如何處理?
    • 根本不需要處理,因為python可以接受任何型別的引數,如果函式的功能相同,name不同的引數型別在python中很可能是相同的程式碼,沒有必要做成兩個不同的函式。
  • 函式功能相同,但引數個數不同,python如何處理?
    • 預設引數,對那些缺少的引數設定為預設引數即可解決問題。

14.新式類和舊式類

python3裡的類全部都是新式類,這裡有一個MRO問題,新式類是廣度優先,舊式類是深度優先。

15.__new__和__init__的區別

__new__是一個靜態方法,而__init__是一個例項方法。
__new__方法會返回一個建立的示例,而__init__什麼都不返回
只有在__new__返回一個cls的例項時後面的__init__才能被呼叫。
當建立一個新例項時呼叫__new__,初始化一個例項時用__init__
__metaclass__是建立類時起作用,所以我們可以分別使用。
__metaclass__、__new__、__init__來分別在類建立,例項建立和例項初始化的時候做些手腳。

16.單例模式

class A(object):
    def __new__(cls,*args,**kwargs):
		if not hasattr(cls,'_instance'):
            orig = super(A,cls)
            cls._instance = orig.__new__(cls,*args,**kwargs)
         return cls.__instance

class MyClass(A):
    a = 1

共享屬性

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

class Borg(object):
    _state = {}
    def __new__(cls, *args, **kw):
        ob = super(Borg, cls).__new__(cls, *args, **kw)
        ob.__dict__ = cls._state
        return ob
class MyClass2(Borg):
    a = 1

裝飾器版本
def singleton(cls, *args, **kw):
    instances = {}
    def getinstance():
        if cls not in instances:
            instances[cls] = cls(*args, **kw)
        return instances[cls]
    return getinstance
@singleton
class MyClass:
    pass