Python面試彙總(一)

轉載自github:https://github.com/taizilongxu/interview_python#2-python%E4%B8%AD%E7%9A%84%E5%85%83%E7%B1%BBmetaclass

3 @staticmethod和@classmethod

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

def foo(x):
    print "executing foo(%s)"%(x)

class A(object):
    def foo(self,x):
        print "executing foo(%s,%s)"%(self,x)

    @classmethod
    def class_foo(cls,x):
        print "executing class_foo(%s,%s)"%(cls,x)

    @staticmethod
    def static_foo(x):
        print "executing static_foo(%s)"%x

a=A()

這裏先理解下函數參數裏面的self和cls.這個self和cls是對類或者實例的綁定,對於一般的函數來說我們可以這麼調用foo(x),這個函數就是最常用的,它的工作跟任何東西(類,實例)無關.對於實例方法,我們知道在類裏每次定義方法的時候都需要綁定這個實例,就是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 類變量和實例變量

類變量:

​ 是可在類的所有實例之間共享的值(也就是說,它們不是單獨分配給每個實例的)。例如下例中,num_of_instance 就是類變量,用於跟蹤存在着多少個Test 的實例。

實例變量:

實例化之後,每個實例單獨擁有的變量。

class Test(object):  
    num_of_instance = 0  
    def __init__(self, name):  
        self.name = name  
        Test.num_of_instance += 1  
  
if __name__ == '__main__':  
    print Test.num_of_instance   # 0
    t1 = Test('jack')  
    print Test.num_of_instance   # 1
    t2 = Test('lucy')  
    print t1.name , t1.num_of_instance  # jack 2
    print t2.name , t2.num_of_instance  # lucy 2

補充的例子

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 Person:
    name=[]

p1=Person()
p2=Person()
p1.name.append(1)
print p1.name  # [1]
print p2.name  # [1]
print Person.name  # [1]

參考:http://stackoverflow.com/questions/6470428/catch-multiple-exceptions-in-one-line-except-block

 

5 Python自省

這個也是python彪悍的特性.

自省就是面向對象的語言所寫的程序在運行時,所能知道對象的類型.簡單一句就是運行時能夠獲得對象的類型.比如type(),dir(),getattr(),hasattr(),isinstance().

a = [1,2,3]
b = {'a':1,'b':2,'c':3}
c = True
print type(a),type(b),type(c) # <type 'list'> <type 'dict'> <type 'bool'>
print isinstance(a,list)  # True

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內部的名字,用來區別其他用戶自定義的命名,以防衝突,就是例如__init__(),__del__(),__call__()這些特殊方法。是一些 Python 的“魔術”對象,如類成員的 __init__、__del__、__add__、__getitem__ 等,以及全局的 __file__、__name__ 等。 Python 官方推薦永遠不要將這樣的命名方式應用於自己的變量或函數

_foo:一種約定,用來指定變量私有.程序員用來指定私有變量的一種方式.不能用from module import * 導入,其他方面和公有一樣訪問;首先是單下劃線開頭,這個被常用於模塊中,在一個模塊中以單下劃線開頭的變量和函數被默認當作內部函數,如果使用 from a_module import * 導入時,這部分變量和函數不會被導入。不過值得注意的是,如果使用 import a_module 這樣導入模塊,仍然可以用 a_module._some_var 這樣的形式訪問到這樣的對象。

__foo:這個有真正的意義:解析器用_classname__foo來代替這個名字,以區別和其他類相同的命名,它無法直接像公有成員一樣隨便訪問,通過對象名._類名__xxx這樣的方式可以訪問。雙下劃線開頭的命名形式在 Python 的類成員中使用表示名字改編 (Name Mangling),即如果有一 Test 類裏有一成員 __x,那麼 dir(Test) 時會看到 _Test__x 而非 __x。這是爲了避免該成員的名稱與子類中的名稱衝突。但要注意這要求該名稱末尾沒有下劃線。

詳情見:http://stackoverflow.com/questions/1301346/the-meaning-of-a-single-and-a-double-underscore-before-an-object-name-in-python

或者: http://www.zhihu.com/question/19754941

9 迭代器和生成器

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

這是中文版: http://taizilongxu.gitbooks.io/stackoverflow-about-python/content/1/README.html

這裏有個關於生成器的創建問題面試官有考: 問: 將列表生成式中[]改成() 之後數據結構是否改變? 答案:是,從列表變爲生成器

>>> L = [x*x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x*x for x in range(10))
>>> g
<generator object <genexpr> at 0x0000028F8B774200>

通過列表生成式,可以直接創建一個列表。但是,受到內存限制,列表容量肯定是有限的。而且,創建一個包含百萬元素的列表,不僅是佔用很大的內存空間,如:我們只需要訪問前面的幾個元素,後面大部分元素所佔的空間都是浪費的。因此,沒有必要創建完整的列表(節省大量內存空間)。在Python中,我們可以採用生成器:邊循環,邊計算的機制—>generator

  • 迭代器一定是一個可迭代對象,因爲既有可迭代對象的iter方法,也有可迭代對象不具備的next方法。
  • 但反過來,可迭代對象卻不一定是一個迭代器,但能通過iter函數實現。
  • 迭代器可以通過next函數訪問下一個值,也可以和可迭代對象一樣for循環遍歷。
  • 生成器就是用來創建迭代器的函數,使用yield關鍵字,返回一個生成器。
  • 生成器既是一個可迭代對象,也是一個迭代器。
  • for循環就是迭代器調用next函數依次訪問下一個值。

迭代器就是用於迭代操作的的對象,遵從迭代協議(內部實現了__iter__()和__next__()方法,可以像列表(可迭代對象,只有__iter__()方法)一樣迭代獲取其中的值,與列表不同的是,構建迭代器的時候,不像列表一樣一次性把數據加到內存,而是以一種延遲計算的方式返回元素,即調用next方法時候返回此值。

生成器本質上也是一個迭代器,自己實現了可迭代協議,與生成器不同的是生成器的實現方式不同,可以通過生成器表達式和生成器函數兩種方式實現,代碼更簡潔。生成器和迭代器都是惰性可迭代對象,只能遍歷一次,數據取完拋出Stopiteration異常

10 *args and **kwargs

*args**kwargs只是爲了方便並沒有強制使用它們.

當你不確定你的函數裏將要傳遞多少參數時你可以用*args.例如,它可以傳遞任意數量的參數:

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

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

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

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

def table_things(titlestring, **kwargs)

*args**kwargs可以同時在函數的定義中,但是*args必須在**kwargs前面.

當調用函數時你也可以用***語法.例如:

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

a = 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

14 新式類和舊式類

這個面試官問了,我說了老半天,不知道他問的真正意圖是什麼.

stackoverflow

這篇文章很好的介紹了新式類的特性: http://www.cnblogs.com/btchenguang/archive/2012/09/17/2689146.html

新式類很早在2.2就出現了,所以舊式類完全是兼容的問題,Python3裏的類全部都是新式類.這裏有一個MRO問題可以瞭解下(新式類繼承是根據C3算法,舊式類是深度優先),<Python核心編程>裏講的也很多.

一箇舊式類的深度優先的例子

class A():
    def foo1(self):
        print "A"
class B(A):
    def foo2(self):
        pass
class C(A):
    def foo1(self):
        print "C"
class D(B, C):
    pass

d = D()
d.foo1()

# A

按照經典類的查找順序從左到右深度優先的規則,在訪問d.foo1()的時候,D這個類是沒有的..那麼往上查找,先找到B,裏面沒有,深度優先,訪問A,找到了foo1(),所以這時候調用的是A的foo1(),從而導致C重寫的foo1()被繞過

Python中類分兩種:舊式類和新式類:

新式類都從object繼承,經典類不需要。

新式類的MRO(method resolution order 基類搜索順序)算法採用C3算法廣度優先搜索,而舊式類的MRO算法是採用深度優先搜索

新式類相同父類只執行一次構造函數,經典類重複執行多次。

  1. Python 2.x中默認都是經典類,只有顯式繼承了object纔是新式類
  2. Python 3.x中默認都是新式類,經典類被移除,不必顯式的繼承object

所以,爲了確保自己使用的是新式類,有兩種以下方法:

1. 元類,在類模塊代碼的最前面加入如下代碼 __metaclass__ = classname(自定義的某個新式類)。

2. 類都從內建類object直接或者間接地繼承。

Python2.x中:

1

2

3

4

5

6

7

8

class A:

 pass

class B:

 pass

class C(B):

 pass

class D(C,A):

 pass

執行順序爲:D->C->B,->A

1

2

3

4

5

6

7

8

class A(object):

 pass

class B(object):

 pass

class C(object):

 pass

class D(A,B,C):

 pass

執行順序爲: D->A->B->C->Object

15 __new____init__的區別

這個__new__確實很少見到,先做了解吧.

  1. __new__是一個靜態方法,而__init__是一個實例方法.
  2. __new__方法會返回一個創建的實例,而__init__什麼都不返回.
  3. 只有在__new__返回一個cls的實例時後面的__init__才能被調用.
  4. 當創建一個新實例時調用__new__,初始化一個實例時用__init__.

stackoverflow

ps: __metaclass__是創建類時起作用.所以我們可以分別使用__metaclass__,__new____init__來分別在類創建,實例創建和實例初始化的時候做一些小手腳.

__new__方法:類級別的方法
特性:

1.是在類準備將自身實例化時調用,並且至少需要傳遞一個參數cls,此參數在實例化時由python解釋器自動提供;

2.始終是類的靜態方法,即使沒有被加上靜態方法裝飾器;

3.必須要有返回值,返回實例化出來的實例;在自己實現__new__()時需要注意:可以return父類(通過super(當前類名,cls)).__new__出來的實例,
或者直接是object的__new__出來的實例

    class A(object):
        pass
     
    a=A()       # 默認調用父類object的__new__()方法來構造該類的實例
    print (a)   # 返回的是<__main__.A object at 0x0000024352732630>

    class A(object):
     
        def __new__(cls):
            "重寫__new__方法"
            return "abc"
     
    a=A()
    print (a)        # 'abc',實例化對象取決於__new__方法,__new__返回什麼就是什麼
    print (type(a))  # <class 'str'>

    通過__new__()方法實現單例
     
    class Singleton(object):
     
        def __new__(cls,*args,**kwargs):
            if not hasattr(cls,"_instance"):
                cls._instance=super(Singleton,cls).__new__(cls)
            return cls._instance
     
    a=Singleton()
    b=Singleton()
    c=Singleton()
    print (a)
    print (b)
    print (c)
     
    輸出結果:
    <__main__.Singleton object at 0x000002474C92D550>
    <__main__.Singleton object at 0x000002474C92D550>
    <__main__.Singleton object at 0x000002474C92D550>

 
__init__方法:實例級別的方法
特性:

1.有一個參數self,該self參數就是__new__()返回的實例;

2.__init__()在__new()的基礎上完成初始化動作,不需要返回值;

3.若__new__()沒有正確返回當前類cls的實例,那__init__()將不會被調用

4.創建的每個實例都有自己的屬性,方便類中的實例方法調用;

 

對比下面代碼理解會更加清晰:

    class B():
        def __new__(cls):
            print ("__new__方法被執行")
     
        def __init__(self):
            print ("__init__方法被執行")
     
    b=B()   
    結果:
    __new__方法被執行

    class B():
        def __new__(cls):
            print ("__new__方法被執行")
            return super(B,cls).__new__(cls)
     
        def __init__(self):
            print ("__init__方法被執行")
     
    b=B()
    結果:
    __new__方法被執行
    __init__方法被執行

16 單例模式

​ 單例模式是一種常用的軟件設計模式。在它的核心結構中只包含一個被稱爲單例類的特殊類。通過單例模式可以保證系統中一個類只有一個實例而且該實例易於外界訪問,從而方便對實例個數的控制並節約系統資源。如果希望在系統中某個類的對象只能存在一個,單例模式是最好的解決方案。

__new__()__init__()之前被調用,用於生成實例對象。利用這個方法和類的屬性的特點可以實現設計模式的單例模式。單例模式是指創建唯一對象,單例模式設計的類只能實例 這個絕對常考啊.絕對要記住1~2個方法,當時面試官是讓手寫的.

單例模式就是確保一個類只有一個實例.當你希望整個系統中,某個類只有一個實例時,單例模式就派上了用場.

1 使用__new__方法

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

class MyClass(Singleton):
    a = 1

2 共享屬性

創建實例時把所有實例的__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

3 裝飾器版本

def singleton(cls):
    instances = {}
    def getinstance(*args, **kw):
        if cls not in instances:
            instances[cls] = cls(*args, **kw)
        return instances[cls]
    return getinstance

@singleton
class MyClass:
  ...

4 import方法

作爲python的模塊是天然的單例模式

# mysingleton.py
class My_Singleton(object):
    def foo(self):
        pass

my_singleton = My_Singleton()

# to use
from mysingleton import my_singleton

my_singleton.foo()

實現單例模式的具體方法可參照博客:https://www.jianshu.com/p/6a1690f0dd00  具體不詳細展開 

只寫一種(推薦):基於__new__方法實現的單例模式(推薦使用,方便)
知識點:
1> 一個對象的實例化過程是先執行類的__new__方法,如果我們沒有寫,默認會調用object的__new__方法,返回一個實例化對象,然後再調用__init__方法,對這個對象進行初始化,我們可以根據這個實現單例.
2> 在一個類的__new__方法中先判斷是不是存在實例,如果存在實例,就直接返回,如果不存在實例就創建.

# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/6 13:36'
import threading


class Singleton(object):
    _instance_lock = threading.Lock()

    def __init__(self, *args, **kwargs):
        pass

    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            with Singleton._instance_lock:
                if not hasattr(cls, '_instance'):
                    Singleton._instance = super().__new__(cls)

            return Singleton._instance


obj1 = Singleton()
obj2 = Singleton()
print(obj1, obj2)


def task(arg):
    obj = Singleton()
    print(obj)


for i in range(10):
    t = threading.Thread(target=task, args=[i, ])
    t.start()

附:super函數的用法:https://www.jianshu.com/p/8ddb595628d1



 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章