改善Python代碼的建議-內部機制

《編寫高質量代碼:改善Python程序的91個建議》筆記之內部機制:知其然知其所以然

1.理解built-in objects

在Python2.x中,默認都是經典類,只有顯式繼承了object纔是新式類,即:

class Person(object):pass 新式類寫法

class Person():pass 經典類寫法

class Person:pass 經典類寫法

 

在Python 3.x中取消了經典類,默認都是新式類,並且不必顯式的繼承object,也就是說:

class Person(object):pass

class Person():pass

class Person:pass

三種寫法並無區別,推薦第一種

 

若一個類定義時繼承自object類或者內建類型,則認爲它是一個新式類的理解是不對的,應當通過元類的類型來確定類的類型,古典類的元類爲type.ClassType,新式類的元類爲type類。

新式類的優點在於能夠基於內建類型構建新的用戶類型,支持property和描述特性等。

新式類 vs 古典類:

MRO(Method Resolution Order):方法解析順序。菱形繼承是在多繼承設計時需要儘量避免的問題。

新式類多繼承搜索順序MRO是廣度優先:先在水平方向查找,再向上查找;

古典類多繼承搜索順序MRO是深度優先:先深入繼承樹左側查找,返回,開始查找右側;

 

2.__init__()不是構造方法

__init__()不是真正意義上的構造方法,__init__()所做的工作是在類的對象創建好之後進行變量的初始化。

__new__()方法纔會真正創建實例,是類的構造方法。

 

object.__new__(cls, [args…]):cls代表類,args爲參數列表。

1)它是靜態方法;

2)一般需要返回類的對象,當返回類的對象時會自動調用__init__()方法進行初始化,若沒有對象返回,則__init__()方法不會被調用;

3)用於創建控制實例;

4)一般不需要覆蓋__new__(),當子類繼承自不可變類型,如str,int,unicode或tuple時,需要覆蓋該方法,覆蓋時必須保持一致,不一致將導致異常。

object.__init__(self, [args…]):self代表實例對象,args爲參數列表。

1)它是實例方法;

2)不需要顯式返回,默認爲None,否則會在運行時拋出typeError;

3)用於控制實例初始化。

 

需要覆蓋__new__()方法的情況:

1.當類繼承(如str,int,unicode,tuple或者frozenset等)不可變類型且默認的__new__()方法不能滿足需求的時候。

2.用來實現工廠模式或者單例模式或者元類編程時,需要使用__new__()控制對象的創建。

3.作爲用來初始化的__init__()方法在多繼承的情況下,子類的__init__()方法若不顯式調用父類的__init__()方法,則父類的__init__()方法不會被調用。

 

3.理解名字查找機制

Python中所有的變量名都在複製時生成,對任何變量名的創建,查找或者改變都會在命名空間中進行。變量名所在的命名空間直接決定了其所能訪問到的範圍,即變量的作用域。Python的作用域自2.2之後分爲以下4種:

    局部作用域,全局作用域,嵌套作用域,內置作用域。

Python變量查找順序遵循變量解析機制LEGB法則:

    1)在最內層的範圍內查找,一般而言,就是在函數內部,即在locals()裏面查找;

    2)在模塊內查找,即在globals()裏面查找;

    3)在外層查找,即在內置模塊中查找,即在__builtin__中查找。

 

4.self參數

self:在類中當定義實例方法時需要將第一個參數顯式聲明爲self,調用時不需要傳入該參數。可以使用self.x來訪問實例變量,也可以在類中使用self.m()來訪問實例方法。self表示的是實例對象本身,及對應類在內存中的地址。self是對對象本身的引用。

需要self的原因:

1)設計Python語言之初借鑑其他語言如Moudla-3中方法會顯式地在參數列表中傳入self;

2)Python本身的動態性決定了使用self能夠帶來一定便利,在調用時決定應該傳入哪個對象;

3)在存在同名的局部變量及實例變量的情況下使用self使得實例變量更容易被區分。

 

5.熟悉Python對象協議

類型轉換協議:如__str__(),__int__();

比較大小的協議:依賴於__cmp__()方法,這是Python對==,!=,<,>等操作符的進行重載的支撐機制;

數值類型相關協議:數值運算符如__add__(),位運算符__or__(),運算賦值符__iadd__()等;

容器類型協議:內置函數len(),__getitem__(),__setitem__(),__delitem__()等;

可調用對象協議:類似函數對象,能夠讓類實例表現得像函數一樣,讓每一個函數調用都不同;

上下文管理器協議:對with語句的支持,該協議通過__enter__()和__exit__()實現對資源的清理,確保資源無論在什麼情況下都會正常清理。

 

6.理解GIL的侷限性

    GIL(Global Interpreter Lock)全局解釋器鎖:是Python虛擬機上用作互斥線程的一種機制,用於保證任何情況下虛擬機中只會有一個線程被運行,其他線程都處於等待GIL被釋放的狀態,這保證了對虛擬機內部資源訪問的互斥性。

    在單核CPU上,GIL對多線程的執行沒有太大影響,因爲單核上的多線程本質上就是順序執行的。對於多核CPU來說,多線程並不能明顯提升效率,甚至在頻繁IO操作的情況下,由於存在需要多次釋放和申請GIL的情形,效率反而會下降。

 

7.對象的管理與垃圾回收

    Python管理內存的方式:使用計數器(Reference counting)的方式來管理內存中的對象,針對每一個對象維護一個引用計數值來表示該對象當前有多少個引用。當其他對象引用該對象時,其引用計數會增加1,而刪除一個對當前對象的引用,其引用計數會減1。只有當引用計數的值爲0時該對象纔會被垃圾收集器回收,因爲它表示這個對象不再被其他對象引用,是個不可達對象。引用計數算法最明顯的缺點是無法解決循環引用的問題,即兩個對象相互引用。

    對於由循環引用而導致的內存泄露的情況,Python自帶一個gc模塊,用來跟蹤對象的”入引用(incoming reference)“和”出引用(outgoing reference)“,並找出複雜數據結構之間的循環引用,同時回收內存垃圾。觸發垃圾回收的兩種方式:一種是通過顯示地調用gc.collect()進行垃圾回收,還有一種是在創建新的對象爲其分配內存的時候,檢查threshold閾值,當對象的數量超過threshold時自動進行垃圾回收。

    gc.garbage返回的是由於循環引用而產生的不可達的垃圾對象的列表,輸出爲空表示內存中此時不存在垃圾對象。

    gc.collect顯示所有收集和銷燬的對象的數目。

 

 

 

 

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