Python 面向對象

面向過程的程序設計的核心是過程(流水線式思惟),過程即解決問題的步驟,面向過程的設計就比如精心設計好一條流水線,考慮周全何時處理什麼東西。

  • 優勢是:極大的下降了程序的複雜度;
  • 缺點是:可擴展性差,修改代碼麻煩;
  • 應用場景:一旦完成基本不多改變的場景,著名的例子有Linux內核,git,以及Apache HTTP Server等。

面向對象的程序設計的核心是對象(上帝式思惟),要理解對象爲什麼物,必須把本身當成上帝,上帝眼裏世間存在的萬物皆爲對象,不存在的也能夠創造出來。面向對象的程序設計比如如來設計西遊記,如來要解決的問題是把經書傳給東土大唐,如來想了想解決這個問題須要四我的:唐僧,沙和尚,豬八戒,孫悟空,每一個人都有各自的特徵和技能(這就是對象的概念,特徵和技能分別對應對象的數據屬性和方法屬性),然而這並很差玩,因而如來又安排了一羣妖魔鬼怪,爲了防止師徒四人在取經路上被搞死,又安排了一羣神仙保駕護航,這些都是對象。而後取經開始,師徒四人與妖魔鬼怪神仙交互着直到最後取得真經。如來根本不會管師徒四人按照什麼流程去取。python

 

面向對象的程序設計git

  • 優勢:解決了程序的擴展性。對某一個對象單獨修改,會馬上反映到整個體系中,如對遊戲中一我的物參數的特徵和技能修改都很容易。
  • 缺點:可控性差,沒法向面向過程的程序設計流水線式的能夠很精準的預測問題的處理流程與結果,面向對象的程序一旦開始就由對象之間的交互解決問題,沒法預測最終結果。因而咱們常常看到一個遊戲人某一參數的修改極有可能致使bug的技能出現,一刀砍死3我的,這個遊戲就失去平衡。
  • 應用場景:需求常常變化的軟件,通常需求的變化都集中在用戶層,互聯網應用,企業內部軟件,遊戲等都是面向對象的程序設計大顯身手的好地方。

面向對象的程序設計並非所有。對於一個軟件質量來講,面向對象的程序設計只是用來解決擴展性。編程

  

面向對象技術簡介

  • 類(Class): 用來描述具備相同的屬性和方法的對象的集合。它定義了該集合中每一個對象所共有的屬性和方法。對象是類的實例。
  • 類變量:類變量在整個實例化的對象中是公用的。類變量定義在類中且在函數體以外。類變量一般不做爲實例變量使用。
  • 數據成員:類變量或者實例變量, 用於處理類及其實例對象的相關的數據。
  • 方法重寫:若是從父類繼承的方法不能知足子類的需求,能夠對其進行改寫,這個過程叫方法的覆蓋(override),也稱爲方法的重寫。
  • 局部變量:定義在方法中的變量,只做用於當前實例的類。
  • 實例變量:在類的聲明中,屬性是用變量來表示的。這種變量就稱爲實例變量,是在類聲明的內部可是在類的其餘成員方法以外聲明的。
  • 繼承:即一個派生類(derived class)繼承基類(base class)的字段和方法。繼承也容許把一個派生類的對象做爲一個基類對象對待。例如,有這樣一個設計:一個Dog類型的對象派生自Animal類,這是模擬"是一個(is-a)"關係(例圖,Dog是一個Animal)。
  • 實例化:建立一個類的實例,類的具體對象。
  • 方法:類中定義的函數。
  • 對象:經過類定義的數據結構實例。對象包括兩個數據成員(類變量和實例變量)和方法。

 

因爲Python是動態語言,根據類建立的實例能夠任意綁定屬性。
給實例綁定屬性的方法是經過實例變量,或者經過self變量。安全

self表明類的實例,而非類

類名加括號就是實例化,會自動觸發__init__函數的運行,能夠用它來爲每一個實例定製本身的特徵。數據結構

類的方法與普通的函數只有一個特別的區別——它們必須有一個額外的第一個參數名稱, 按照慣例它的名稱是 self。ide

 

實例屬性屬於各個實例全部,互不干擾;類屬性雖然歸類全部,但類的全部實例均可以訪問到。函數

在編寫程序的時候,千萬不要對實例屬性和類屬性使用相同的名字,由於相同名稱的實例屬性將屏蔽掉類屬性,可是當你刪除實例屬性後,再使用相同的名稱,訪問到的將是類屬性。測試

>>> class Student(object):
...     name = 'Student'
...
>>> s = Student() # 建立實例s
>>> print(s.name) # 打印name屬性,由於實例並無name屬性,因此會繼續查找class的name屬性
Student
>>> print(Student.name) # 打印類的name屬性
Student
>>> s.name = 'Michael' # 給實例綁定name屬性
>>> print(s.name) # 因爲實例屬性優先級比類屬性高,所以,它會屏蔽掉類的name屬性
Michael
>>> print(Student.name) # 可是類屬性並未消失,用Student.name仍然能夠訪問
Student
>>> del s.name # 若是刪除實例的name屬性
>>> print(s.name) # 再次調用s.name,因爲實例的name屬性沒有找到,類的name屬性就顯示出來了
Student
View Code

 

 可使用如下函數的方式來訪問屬性:spa

  • getattr(obj, name[, default]) : 訪問對象的屬性。
  • hasattr(obj,name) : 檢查是否存在一個屬性。
  • setattr(obj,name,value) : 設置一個屬性。若是屬性不存在,會建立一個新屬性。
  • delattr(obj, name) : 刪除屬性。 

Python內置類屬性

  • __dict__ : 類的屬性字典,或者說名稱空間
  • __doc__ :類的文檔字符串
  • __name__: 類名
  • __module__: 類定義所在的模塊(類的全名是'__main__.className',若是類位於一個導入模塊mymod中,那麼className.__module__ 等於 mymod)
  • __bases__ : 類的全部父類構成元素(包含了一個由全部父類組成的元組)

 

咱們定義的類的屬性到底存到哪裏了?有兩種方式查看
 dir(類名):查出的是一個名字列表
類名.__dict__:查出的是一個字典,key爲屬性名,value爲屬性值設計

 

python對象銷燬(垃圾回收)

Python 使用了引用計數這一簡單技術來跟蹤和回收垃圾。

在 Python 內部記錄着全部使用中的對象各有多少引用。

 

一個內部跟蹤變量,稱爲一個引用計數器。

當對象被建立時, 就建立了一個引用計數, 當這個對象再也不須要時, 也就是說, 這個對象的引用計數變爲0 時, 它被垃圾回收。可是回收不是"當即"的, 由解釋器在適當的時機,將垃圾對象佔用的內存空間回收。

垃圾回收機制不只針對引用計數爲0的對象,一樣也能夠處理循環引用的狀況。循環引用指的是,兩個對象相互引用,可是沒有其餘變量引用他們。這種狀況下,僅使用引用計數是不夠的。Python 的垃圾收集器其實是一個引用計數器和一個循環垃圾收集器。做爲引用計數的補充, 垃圾收集器也會留心被分配的總量很大(及未經過引用計數銷燬的那些)的對象。 在這種狀況下, 解釋器會暫停下來, 試圖清理全部未引用的循環。

析構函數 __del__ ,__del__在對象銷燬的時候被調用,當對象再也不被使用時,__del__方法運行

 

類的繼承

面向對象的編程帶來的主要好處之一是代碼的重用,實現這種重用的方法之一是經過繼承機制。

經過繼承建立的新類稱爲子類(Subclass)派生類,被繼承的類稱爲基類父類超類(Base class、Super class)

繼承語法

class 派生類名(基類名) ...

在python中繼承中的一些特色:

  • 一、若是在子類中須要父類的構造方法就須要顯示的調用父類的構造方法,或者不重寫父類的構造方法。
  • 二、在調用基類的方法時,須要加上基類的類名前綴,且須要帶上 self 參數變量。區別在於類中調用普通函數時並不須要帶上 self 參數
  • 三、Python 老是首先查找對應類型的方法,若是它不能在派生類中找到對應的方法,它纔開始到基類中逐個查找。(先在本類中查找調用的方法,找不到纔去基類中找)。

若是在繼承元組中列了一個以上的類,那麼它就被稱做"多重繼承" 。

語法:

派生類的聲明,與他們的父類相似,繼承的基類列表跟在類名以後,以下所示:

class SubClassName (ParentClass1[, ParentClass2, ...]): ...


繼承的定義
繼承是一種建立新類的方式,在python中,新建的類能夠繼承一個或多個父類,父類又可稱爲基類或超類,新建的類稱爲派生類或子類。

分類:單繼承和多繼承

class ParentClass1: #定義父類
    pass

class ParentClass2: #定義父類
    pass

class SubClass1(ParentClass1): #單繼承,基類是ParentClass1,派生類是SubClass
    pass

class SubClass2(ParentClass1,ParentClass2): #python支持多繼承,用逗號分隔開多個繼承的類
    pass
    
print(SubClass2.__base__)
print(SubClass2.__bases__)
print(ParentClass1.__base__)

輸出
<class '__main__.ParentClass1'>
(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
<class 'object'>

若是沒有指定基類,python的類會默認繼承object類,object是全部python類的基類,它提供了一些常見方法(如__str__)的實現。
View Code

 

繼承有什麼好處?最大的好處是子類得到了父類的所有功能。繼承的另外一個好處:多態。
要判斷class的類型,可使用isinstance()函數。isinstance()判斷的是一個對象是不是該類型自己,或者位於該類型的父繼承鏈上。在繼承關係中,若是一個實例的數據類型是某個子類,那它的數據類型也能夠被看作是父類。

多態真正的威力:調用方只管調用,無論細節,這就是著名的「開閉」原則,對擴展開放:容許新增子類;對修改封閉:不須要修改依賴類的函數。

繼承還能夠一級一級地繼承下來,就比如從爺爺到爸爸、再到兒子這樣的關係。而任何類,最終均可以追溯到根類object,這些繼承關係看上去就像一顆倒着的樹。

Python是動態語言,動態語言的「鴨子類型」,它並不要求嚴格的繼承體系,一個對象只要「看起來像鴨子,走起路來像鴨子」,那它就能夠被看作是鴨子。動態語言的鴨子類型特色決定了繼承不像靜態語言那樣是必須的。

 

方法重寫

若是你的父類方法的功能不能知足你的需求,你能夠在子類重寫你父類的方法:

 

基礎重載方法

下表列出了一些通用的功能,你能夠在本身的類重寫:

 

序號 方法, 描述 & 簡單的調用
1 __init__ ( self [,args...] )
構造函數
簡單的調用方法: obj = className(args)
2 __del__( self )
析構方法, 刪除一個對象
簡單的調用方法 : del obj
3 __repr__( self )
轉化爲供解釋器讀取的形式
簡單的調用方法 : repr(obj)
4 __str__( self )
用於將值轉化爲適於人閱讀的形式
簡單的調用方法 : str(obj)
5 __cmp__ ( self, x )
對象比較
簡單的調用方法 : cmp(obj, x)

 

運算符重載

 

 

類屬性與方法

類的私有屬性

__private_attrs:兩個下劃線開頭,聲明該屬性爲私有,不能在類的外部被使用或直接訪問。在類內部的方法中使用時 self.__private_attrs

類的方法

在類的內部,使用 def 關鍵字能夠爲類定義一個方法,與通常函數定義不一樣,類方法必須包含參數 self,且爲第一個參數

類的私有方法

__private_method:兩個下劃線開頭,聲明該方法爲私有方法,不能在類的外部調用。在類的內部調用 self.__private_methods

 

Python不容許實例化的類訪問私有數據,但你可使用 object._className__attrName( 對象名._類名__私有屬性名 )訪問屬性

  

單下劃線、雙下劃線、頭尾雙下劃線說明:

  • __foo__: 定義的是特殊方法,通常是系統定義名字 ,相似 __init__() 之類的。特殊變量能夠直接訪問

  • _foo: 以單下劃線開頭的表示的是 protected 類型的變量,即保護類型只能容許其自己與子類進行訪問,不能用於 from module import *,意思就是,「雖然我能夠被訪問,可是,請把我視爲私有變量,不要隨意訪問」。

  • __foo: 雙下劃線的表示的是私有類型(private)的變量, 只能是容許這個類自己進行訪問了。

 

使用__slots__

若是咱們想要限制實例的屬性怎麼辦?好比,只容許對Student實例添加name和age屬性。
爲了達到限制的目的,Python容許在定義class的時候,定義一個特殊的__slots__變量,來限制該class實例能添加的屬性:

class Student(object):
    __slots__ = ('name', 'age') # 用tuple定義容許綁定的屬性名稱
    
>>> s = Student() # 建立新的實例
>>> s.name = 'Michael' # 綁定屬性'name'
>>> s.age = 25 # 綁定屬性'age'
>>> s.score = 99 # 綁定屬性'score'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'

因爲'score'沒有被放到__slots__中,因此不能綁定score屬性,試圖綁定score將獲得AttributeError的錯誤。

使用__slots__要注意,__slots__定義的屬性僅對當前類實例起做用,對繼承的子類是不起做用的:

>>> class GraduateStudent(Student):
...     pass
...
>>> g = GraduateStudent()
>>> g.score = 9999
View Code

 

實例方法(instance method),類方法(class method)與靜態方法(static method)

實例方法,除靜態方法與類方法外,類的其餘方法都屬於實例方法。

實例方法須要將類實例化後調用,若是使用類直接調用實例方法,須要顯式地將實例做爲參數傳入。

類調用:類名.方法(實例對象名)
對象調用:直接調用(實例對象名.方法)

class Person:

  def run(self):
    pass

class ClassA(object):
    def func_a(self):
        print('Hello Python')

# 使用實例調用實例方法
ca = ClassA()
ca.func_a()

# 若是使用類直接調用實例方法,須要顯式地將實例做爲參數傳入
ClassA.func_a(ca)
View Code

 

類方法傳入的第一個參數爲cls,是類自己。而且,類方法能夠經過類直接調用,或經過實例直接調用。但不管哪一種調用方式,最左側傳入的參數必定是類自己。

類方法使用@classmethod裝飾器來聲明。

class Person:
  @classmethod
  def countPerson(cls):
    pass

類調用:不用手動傳遞第一個參數,會自動的把調用的類自己給傳遞過去
對象調用:不用手動傳遞第一個參數,會自動的把調用的對象對應的類給傳遞過去

class Person:
    @classmethod
    def leifangfa(cls, a):
        print("這是一個類方法", cls, a)

Person.leifangfa(123)

p = Person()
p.leifangfa(666)

# 輸出:
# 這是一個類方法 <class '__main__.Person'> 123
# 這是一個類方法 <class '__main__.Person'> 666
View Code

 

 

靜態方法是指類中無需實例參與便可調用的方法(不須要self參數),靜態方法使用@staticmethod裝飾器來聲明。
類調用:直接調用(類名.方法)
對象調用:直接調用(實例對象名.方法)

class Person:
  @staticemethod
  def countPerson():
    pass

class ClassA(object):
    @staticmethod
    def func_a():
        print('Hello Python')

if __name__ == '__main__':
    ClassA.func_a()  #類調用

ca = ClassA()
ca.func_a() #實例對象調用
View Code
class ClassA(object):
    def func_a():
        print('Hello Python')

if __name__ == '__main__':
    ClassA.func_a()

ca = ClassA()
ca.func_a()

異常信息:
func_a() takes 0 positional arguments but 1 was given
由於func_a沒有聲明爲靜態方法,類實例在調用func_a時,會隱式地將self參數傳入func_a,而func_a自己不接受任何參數,從而引起異常。
View Code

 

小結

1.定義形式上:

a. 類方法和靜態方法都是經過裝飾器實現的,實例方法不是;

b. 實例方法須要傳入self參數,類方法須要傳入cls參數,而靜態方法不須要傳self或者cls參數。

2. 調用方式上:

實例方法只能經過實例對象調用;類方法和靜態方法能夠經過類對象或者實例對象調用,若是是使用實例對象調用的類方法或靜態方法,最終都會轉而經過類對象調用。

3. 應用場景:

a. 實例方法使用最多,能夠直接處理實例對象的邏輯;類方法不須要建立實例對象,直接處理類對象的邏輯;靜態方法將與類對象相關的某些邏輯抽離出來,不只能夠用於測試,還能便於代碼後期維護。

b. 實例方法和類方法,可以改變實例對象或類對象的狀態,而靜態方法不能。

 

metaclass:建立類對象的類

實例對象由類建立出來,類是一個對象,類由元類建立出來。
metaclass容許建立類或者修改類。能夠把類當作是metaclass建立出來的「實例」。

Python解釋器遇到class定義時,先掃描class定義的語法,而後調用type()函數建立出class。

要建立一個class對象,type()函數依次傳入3個參數:
class的名稱;
繼承的父類集合,注意Python支持多重繼承,若是隻有一個父類,別忘了tuple的單元素寫法;
class的方法名稱與函數綁定。

>>> def fn(self, name='world'): # 先定義函數
...     print('Hello, %s.' % name)
...
>>> Hello = type('Hello', (object,), dict(hello=fn)) # 建立Hello class
>>> h = Hello()
>>> h.hello()
Hello, world.
>>> print(type(Hello))
<class 'type'>
>>> print(type(h))
<class '__main__.Hello'>
View Code

 

 

面向對象遵循的設計原則: SOLID

S(Single Responsibility Principle)單一職責原則,一個類只負責一項職責
好處:易於維護, 寫出高內聚的代碼;易於代碼複用

O(Open Closed Principle) 開放封閉原則
對擴展開放
對修改關閉
易於維護, 保證代碼安全性以及擴展性

L(Liskov Substitution Principle) 里氏替換原則,使用基類引用的地方必須能使用繼承類的對象
好處
防止代碼出現不可預知的錯誤
方便針對於基類的測試代碼, 能夠複用在子類上


I(Interface Segregation Principle)接口分離原則
若是一個類包含了過多的接口方法,而這些方法在使用的過程當中並不是"不可分割", 那麼應當把他們進行分離
所謂接口, 在Python中, 能夠簡單的理解爲"抽象方法"
好處
提升接口的重用價值

D(Dependency Inversion Principle)依賴倒置原則 高層模塊不該該直接依賴低層模塊 他們應該依賴抽象類或者接口 好處 利於代碼維護

相關文章
相關標籤/搜索