目錄python
面向對象是一種程序設計思想,它把對象做爲程序的基本單元,一個對象包含了數據和操做數據的函數。但並非全部語言都支持面向對象編程的。簡單的從語言自己來分的話,主要分爲如下三種:(並非說其餘類型的語言很差,只是場景不適合而已,就比如操做系統,基本都是使用C語言編寫的,語言沒有優劣,只有適不適合)數據庫
面向機器
(彙編語言等):抽象成機器指令,機器容易理解。面向過程
(C語言等):作一件事,排除個步驟,第一步幹什麼,第二部幹什麼,若是出現狀況A,作什麼處理,若是出現過程B,作什麼處理。問題規模小,能夠步驟話,循序漸進的處理。面向對象OOP
(Java語言,C++語言,Python等):針對狀況複雜的狀況,好比表示一個大樓的做用,使用函數就比較麻煩了。編程
如今大部分語言都是面向對象的,它適合軟件規模比較大的場景使用數據結構
那什麼是面向對象?咱們能夠簡單來講它就是一套方法論,是 一種認識世界、分析世界的方法論
。將萬事萬物都抽象爲各類對象,在Python中一切皆對象。下面來講一下,主要的名詞含義:app
抽象的概念,是一類事物的共同特徵的集合。函數
用計算機語言來描述,就是屬性(數據)和方法(操做事物的能力)的集合。但凡說分類:都是抽象的概念學習
對象是類的具象、是一個實體。對於咱們每一個個體,都是抽象概念人類的不一樣實體(對象,實例),好比你吃魚,你是對象,魚也是對象,吃是動做.
PS:在python中咱們說類的實例一般會用instance或者object來指代。測試
操做:對象行爲的抽象,用操做名和實現該操做的方法來描述(函數)操作系統
每一個人都是人類的一個單獨實例,都有本身的名字、身高、體重等信息,這些信息是我的的屬性,可是這些信息不能保存在人類中,由於它是抽象的概念,不能保存具體的信息,而咱們每一個人,每一個個體,都是具體的人,就能夠存儲這些具體的屬性,並且不一樣的人具備不一樣的屬性。設計
在Python中一切皆對象,對象是數據和操做的封裝,對象是獨立的,可是對象之間能夠相互做用(你推我,我打你,相互做用)。
目前OOP是最接近人類認知的編程範式
面向對象有三大特色:
封裝(Encapsulation)
,將數據和操做組裝到一塊兒,對外只暴露一些藉口,經過接口訪問對象。(好比駕駛員駕駛汽車,不須要了解汽車的構造,只須要知道使用什麼部件怎麼駕駛皆能夠了,踩了油門就能跑,能夠不瞭解其中的機動原理。)繼承(Inheritance)
,多複用(繼承來的就不用寫了),遵循多繼承少修改原則,OCP(Open-closed Principle),使用繼承來改變,來體現個性(修改實例本身,不要去修改父類)多態(Polymorphism)
,面向對象編程最靈活的地方,動態綁定,好比人吃和貓吃,都是吃,可是不一樣吃又不太同樣。簡單來講就是:同一種方法,在不一樣的子類上,表現方式不一樣。(父類、子類經過繼承聯繫在一塊兒,經過一套方法,就能夠實現不一樣的表現,就是多態)對象是特徵(變量)與技能(函數)的結合,類是一些列對象共有的特徵與技能的結合體.
定義一個類:
class ClassName: 語句塊
規範:
class
關鍵字大駝峯
命名class MyClass: # class 類名稱 '''MyClass Doc''' # 類介紹,經過類對象的__doc__屬性獲取 x = 100 # 類屬性 def showme(self): # 類方法名(函數) print('My Class') # 函數體 showme = lambda self: print('My Class') # 等同於上面定義的函數 p = MyClass() print(p.__doc__)
類對象
:類的定義執行後會產生一個類對象類的屬性
:類定義中的變量和類中定義的方法都是類的屬性類變量
:x是類MyClass的變量因此根據上面例子來講:
經過使用類名加括號來構建實例化一個對象。
a = Person() # 實例化
daxin = Person() xiaoming = Person()
注意:上面的兩次實例化產生的都是不一樣的實例,即使是參數相同。Python類實例化後,會自動調用__init__
方法。這個方法第一個形式參數必須留給self,其餘參數隨意。
在Python中對象的實例化,其實分爲兩個部分,即實例化和初始化。主要對應__new__和__init__方法。
__變量名__
的方法叫作魔術方法
__new__方法比較複雜,通常用在元類的定義和修改上,這裏先記住:__new__方法實例化一個對象的過程當中會調用__init__方法進行初始化,初始化完畢後再由__new__方法將產生的實例對象進行返回,當__new__方法和__init__方法沒有被定義時,會隱式
的向上(父類)查找並調用。
class Person(): def __init__(self, name, age): # 形參 self.name = name # 這裏daxin 賦給 self.name self.age = age # 這裏20 賦給 self.age def sing(self): print('{} is sing'.format(self.name)) daxin = Person('daxin',20) # 實參
注意:
__init__
: 的self是由__new__方法注入進來的,不用手動傳遞,這個self就是實例化後的對象。__init__
: 只是添加了一些定製化的屬性,並不會返回對象。注意:
__init__
方法只有在實例化的時候纔會被調用。
類實例化後必定會得到一個類的實例,就是實例對象。__init__方法的第一個變量self就是實例自己。經過實例化咱們能夠得到一個實例對象,好比上面的daxin,咱們能夠經過daxin.sing()來調用sing方法,可是咱們並無傳遞self參數啊,這是由於類實例化後,獲得一個實例對象,實例對象會綁定
到方法
上,在使用daxin.sing()進行調用時,會把方法的調用者daxin實例,做爲第一個參數self傳入。而self.name就是daxin實例的name屬性,name保存在daxin實例中,而不是Person類中,因此這裏的name被叫作實例變量。
類中定義的方法會存放在類中(僅存一份),而不是實例中,實例直接綁定到方法上。
class Person: def __init__(self,name): self.name = name def sing(self): print('{} is sing'.format(self.name)) daxin = Person('daxin') print(daxin.sing) # <bound method Person.sing of <__main__.Person object at 0x00000198EADD83C8>> 綁定方法 print(Person.sing) # <function Person.sing at 0x00000198EADD99D8> 方法函數
上例中,在調用daxin.sing時,daxin實例是被綁定到了sing方法上,當 a = Person('a') 時,a.sing其實也是把a綁定到了sing方法上,仔細看的話,不一樣實例的sing方法的內存地址是相同的.
實例變量是每個實例本身的變量,是本身獨有的。類變量是類的變量,是類的全部實例共享的屬性和方法。下面咱們從一個例子來看實例變量和類變量
In [2]: class Person: ...: country = 'China' # 類變量 ...: def __init__(self, name, gender): ...: self.name = name # 實例變量 ...: self.gender = gender ...: In [3]: daxin = Person('daxin','male') In [4]: daxin.country Out[4]: 'China' In [5]: daxin.name Out[5]: 'daxin' In [6]: Person.country Out[6]: 'China' In [7]: Person.name --------------------------------------------------------------------------- AttributeError
觀察例子,咱們能夠發現:
實例變量只能經過實例訪問(由於類自己是不會知道誰是他的實力)
類變量能夠經過實例調用,但實例變量是實例私有的,沒法經過類調用。
PS: 獲取類屬性的不一樣的方式
daxin.age daxin.__class__.age Person.age type(daxin).age
有幾個特殊的屬性先來看一下,便於後續理解
特殊屬性 | 含義 |
---|---|
__name__ |
獲取類對象的對象名(返回字符串) |
__class__ |
獲取實例對象的類型(至關於type),type和__class__返回的是一樣的東西 |
__dict__ |
對象的屬性的字典 |
__qualname__ |
類的限定名 |
In [8]: daxin.__class__ Out[8]: __main__.Person In [9]: Person.__class__ Out[9]: type In [10]: type(daxin) Out[10]: __main__.Person In [11]: type(daxin) is Person Out[11]: True In [13]: Person.__name__ Out[13]: 'Person' # 返回字符串
疑問:當類變量和實例變量重名時,到底訪問的是哪一個?
class Person: age = 3 height = 170 def __init__(self, name, age=18): self.name = name self.age = age tom = Person('Tom') jerry = Person('jetty', 20) Person.age = 30 print(1, Person.age, tom.age, jerry.age) print(2, Person.height, tom.height, jerry.height) jerry.height = 175 print(3, Person.height, tom.height, jerry.height) tom.height += 10 print(4, Person.height, tom.height, jerry.height) Person.height += 15 print(5, Person.height, tom.height, jerry.height) Person.weight = 70 print(6, Person.weight, tom.weight, jerry.weight)
分析:
經過觀察發現,當類變量和實例變量重名時,彷佛有必定的查找規則,那麼就要說說__dict__了。
__dict__是一個很是重要的特殊屬性,它是一個存放着對象的屬性信息的字典(對實例來講是字典,對類來講是映射)。
In [14]: Person.__dict__ Out[14]: mappingproxy({'__module__': '__main__', 'country': 'China', '__init__': <function __main__.Person.__init__(self, name, gender)>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}) In [15]: daxin.__dict__ Out[15]: {'name': 'daxin', 'gender': 'male'} In [19]: daxin.__dict__['name'] # 由於__dict__是一個字典,咱們能夠直接經過key來訪問 Out[19]: 'daxin'
經過觀察發現:
方法是記錄在類的
__dict__
中的
實例屬性查找順序: 指的是實例使用.點號來訪問屬性,會先找本身的__dict__,若是沒有,而後經過屬性__class__找到本身的類,而後再在類的__dict__中查找.直接使用實例.__dict__[變量名],不會在類中查找。
爲一個類經過裝飾,增長一些類屬性。例如可否給一個類增長一個NAME類屬性並提供屬性值
def dec(name): def warpper(cls): cls.NAME = name return cls return warpper @dec('tom') # Person = dec('tom')(Person) class Person: pass a = Person() print(a.NAME)
類也能夠做爲一個裝飾器,後面會說
能夠給類加裝飾器,那麼能夠給類中的方法加裝飾器嗎?答案固然是能夠的。
在類中中定義的方法大多都須要傳入一個self參數,用於指定一個實例化對象,用於經過對象來調用這個方法,可是也有例外的狀況
定義在類內部的普通函數,只能經過類來進行調用
In [23]: class Person: ...: def normal_function(): ...: print('normal_function') ...: In [24]: Person.normal_function() normal_function In [25]: test = Person() In [27]: test.normal_function() --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-27-9011f04ffcaa> in <module> ----> 1 test.normal_function() TypeError: normal_function() takes 0 positional arguments but 1 was given In [28]:
注意:
當經過實例訪問時,因爲實例會默認把當前實例看成self傳入函數,而函數並無形參接受因此會報錯。
雖然能夠經過類調用,可是沒人這樣用,也就是說是
禁止
這麼寫的
類方法是給類用的,在使用時會將類自己當作參數傳給類方法的第一個參數,使用@classmethod
裝飾器來把類中的某個函數定義成類方法。
In [31]: class Person: ...: @classmethod ...: def class_method(cls): ...: print('class={0.__name__}({0})'.format(cls)) ...: cls.HEIGHT=170 ...: In [32]: Person.class_method() class=Person(<class '__main__.Person'>) In [35]: daxin = Person() In [36]: daxin.class_method() class=Person(<class '__main__.Person'>) In [37]:
注意:
經過類的實例依舊能夠調用類方法,那是由於當經過實例調用時,@classmethod發現調用者是實例時,會把當前實例的__class__對象,傳入給類方法的cls。
類方法相似於C++、Java中的靜態方法。
靜態方法是一種普通函數,位於類定義的命名空間中,不會對任何實例類型進行操做,它有以下特色
@classmethod
裝飾器修飾的方法,在調用時不會隱式的傳入參數class Person: @staticmethod def static_method(): print('static_method') daxin = Person() Person.static_method() daxin.static_method()
和普通函數的區別是,靜態方法可使用實例調用,普通方法不能夠。
類幾乎能夠調用全部方法,普通函數的調用通常不可能出現,由於不容許這麼定義。實例也幾乎能夠調用全部內部定義的方法,可是調用普通方法時會報錯,緣由是第一參數必須是類的實例self。
class Test: age = 10 def normal_funtion(): print('normal_function') @classmethod def class_method(cls): print(cls.__name__) @staticmethod def static_method(): # Test.age print('static_method') daxin = Test() daxin.normal_funtion() # 沒法調用,由於經過實例調用時,實例會被看成第一個參數傳給normal_funcion。 daxin.class_method() # 能夠調用,當@classmethod檢測到是經過實例調用時,會把當前實例的__class__,看成cls傳遞,能夠訪問類屬性,但沒法訪問實例屬性 daxin.static_method() # 能夠調用,當@staticmethod檢測到是經過實例調用時,會在當前實例的類中調用靜態方法,沒法訪問類或實例的屬性,可是能夠訪問全局變量(好比全局變量Test類) Test.normal_funtion() # 能夠調用,類調用時不傳遞參數,恰好normal_function也不須要參數,沒法訪問類或實例的屬性 Test.class_method() # 能夠調用,類方法,經過類調用時,類會被看成cls傳遞給函數,能夠訪問類屬性,沒法訪問實例屬性 Test.static_method() # 能夠調用,靜態方法,等於定義在類內的函數,沒法訪問類或實例的屬性,可是能夠訪問全局變量(好比全局變量Test類)
注意:
體會:
class Test: age = 10 def normal_funtion(abc): print('{}'.format(abc)) Test.normal_funtion(Test) Test.normal_funtion(123) a = Test() a.normal_funtion()
normal_function依舊是一個普通方法而已,加了參數發現類和實例均可以調用了,那是由於以前實例沒法調用是由於實例調用時默認傳遞給函數做爲第一個參數,那麼我給普通函數加一個形參接受就能夠了。沒有場景這樣使用,這裏只作學習瞭解。
封裝成類還能夠控制什麼屬性可讓別人訪問,什麼方法不能讓別人訪問,這就叫作訪問控制。
使用 雙下劃線開頭 的屬性名,就是私有屬性,現有以下例子:
class Person: def __init__(self, name, age): self.name = name self.age = age daxin = Person('daxin', 18) print(daxin.age)
咱們能夠在外面直接使用daxin.age來訪問daxin的age屬性,可是若是這個age是銀行卡密碼,不能讓別人知道該怎麼辦呢,Python提供了一種私有屬性來解決
class Person: def __init__(self, name, age): self.name = name self.__age = age daxin = Person('daxin', 20) print(daxin.__age)
使用__開頭的屬性被稱爲私有屬性,這種屬性在類的外部是沒法直接進行訪問的,前面所說__dict__中會存放類的屬性信息,那麼咱們來看一下daxin實例的__dict__
是怎麼樣的。
print(daxin.__dict__) {'name': 'daxin', '_Person__age': 20}
根據上面結果能夠得知:私有屬性的本質上實際上是屬性更名,設置self.__age屬性時,__age 變爲了 _Person__age(_類名__屬性名)
class Person: def __init__(self,name, age): self.name = name self.__age = age daxin = Person('daxin',18) daxin.__age = 100 print(daxin.__dict__) # {'name': 'daxin', '_Person__age': 18, '__age': 100}
只有在類中定義的私有變量纔會被更名,上面咱們雖然指定了實例的變量__age,但因爲是在類外定義的,因此它並不會變形,就真的產生了一個__age屬性,而在類內定義的,因爲變型了,因此不會覆蓋。觀察__dict__
就能夠看出結果。
咱們知道私有屬性在定義時會被更名,而且知道更名後的屬性名稱,那麼咱們是否就能夠修改了呢?
class Person: def __init__(self,name, age): self.name = name self.__age = age def get_age(self): return self.__age daxin = Person('daxin',18) daxin._Person__age = 100 print(daxin.get_age()) # 100
經過結果咱們能夠知道,只要知道了私有變量的名稱,就能夠直接從外部訪問,而且修改它。但並不建議這麼作!
Java等其餘語言,比較嚴格,私有在外面是絕對訪問不到的。
保護變量(protected),其實Python並不支持保護變量,是開發者本身的不成文的約定。那什麼是保護變量呢?在變量前使用 一個下劃線
的變量稱爲保護變量。
class Person: def __init__(self,name): self._name = name daxin = Person('daxin') print(daxin.__dict__) # {'_name': 'daxin'} print(daxin._name) # daxin
註釋:
外部依舊能夠看到而且調用
若是看見這種變量,就如同私有變量,儘可能不要直接使用
前面說了私有屬性,那麼私有方法和私有屬性是類似的,使用單/雙下劃線開頭的方法稱爲私有方法(不能已雙下劃線結尾)
class Person: def __init__(self,name): self._name = name def __age(self): return self._name print(Person.__dict__) # {'__module__': '__main__', '__init__': <function Person.__init__ at 0x00000228BCB22158>, '_Person__age': <function Person.__age at 0x00000228BCB221E0>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
經過上面__dict__的內容,咱們發現私有方法和私有屬性是相同的,都會被更名,因此知道了私有方法真正的名字,咱們依舊能夠在外部進行調用,可是不建議。
單下劃線的方法,也和變量相同,解釋器不會作任何變型,只是告訴你,它是一個私有方法,不建議直接使用。
能夠經過修改或替換類的成員。使用者調用的方式沒有改變,可是,類提供的功能可能已經改變了。(好比前面的類裝飾器),一般稱做猴子補丁(Monkey Patch)
在運行時,對屬性、方法、函數等進行動態替換。其目的每每是爲了經過替換、修改來加強、擴展原有代碼的能力。
class Person: def __init__(self, name): self.name = name def sing(self): print('{} is sing'.format(self.name)) def monkeypatch(): # 猴子補丁,用於動態修改Person中某個方法 Person.sing = lambda self: print('hello world') # 能夠來自於不一樣包中的某個函數,這裏只是使用lambda舉例 monkeypatch() # 執行後,Person.sing函數就被替換掉了 daxin = Person('daxin') daxin.sing() # 'hello world'
通常狀況下是不建議使用的,可是在某些場景下,好比我替換的方法原來是鏈接數據庫獲取數據的,反覆鏈接數據庫測試不是很方便,因此在這種狀況下,咱們使用補丁的方式,返回部分目標數據就行了,不用每次都去數據庫取數據。
被屬性裝飾器裝飾的方法,就變成了屬性了。是否是很拗口?簡單來講就是:屬性裝飾器把一個方法變成屬性,進行調用。這麼作的目的能夠把某些屬性保護起來,不讓外部訪問(經過前面私有屬性的瞭解,咱們知道這是不可能的,嘿嘿嘿)。它主要由下面三種組成,能夠組合使用也能夠單獨使用。
@property
:標識下面的方法爲屬性方法,同時激活setter,deleter@方法.setter
:設置屬性時調用方法@方法.deleter
:刪除屬性時調用方法不使用屬性裝飾器時,咱們爲了隱藏某個屬性可使用以下方法:
class Person: def __init__(self, name, age=18): self.name = name self.__age = age def age(self): # 通常稱爲getter方法 return self.__age def setage(self, value): # 通常稱爲setter方法 self.__age = value daxin = Person('daxin') daxin.setage(30) print(daxin.age())
使用起來很彆扭,爲何獲取age屬性要加括號執行方法呢?如何能讓用戶在使用age或者setage時不要認爲他們是在調用函數,而是一個屬性呢,下面使用屬性裝飾器property來完成這個需求
class Person: def __init__(self, name, age=18): self.name = name self.__age = age @property def age(self): # 通常稱爲getter方法 return self.__age daxin = Person('daxin') print(daxin.age)
添加了@property裝飾器之後,被他裝飾的函數,就能夠像普通的屬性來訪問了(daxin.age,就是daxin.age()的返回值),那是否可使用daxin.age=100來設置age屬性的值呢?
daxin.age = 100 >> AttributeError: can't set attribute
沒法設置的,是由於property裝飾的函數是隻讀的,若是要使用daxin.age = 100 來賦值時,還須要一個setter裝飾器來裝飾一個設置屬性的函數,而且這個函數必須和property裝飾的函數的名稱相同。那若是要刪除呢,天然就觸發了deleter裝飾器了,咱們在方法中,刪除屬性便可。
class Person: def __init__(self, name, age=18): self.name = name self.__age = age @property def age(self): # 通常稱爲getter方法 return self.__age @age.setter # 被裝飾的函數.setter(設置一個屬性的值時觸發) def age(self,value): self.__age = value @age.deleter # 刪除一個屬性時觸發(很不經常使用) def age(self): del self.__age daxin = Person() print(daxin.age) # 觸發@property裝飾過的age daxin.age = 200 # 觸發age.setter del daxin.age # 觸發age.deleter
這樣使用起來就比較方便了,固然property還提供了另外一種寫法property(getter,settr,deleter,'description')
,由於property其實是一個class類而已。
class Person: def __init__(self, name, age=18): self.name = name self.__age = age def getage(self): return self.__age def setage(self, value): self.__age = value def delage(self): del self.__age age = property(getage,setage,delage,'age property')
例:快速實現一個只讀屬性
class Person: def __init__(self, name, age=18): self.name = name self.__age = age age = property(lambda self:self.__age) # 快捷包裝一個只讀屬性。
由於lambda不支持等號,而setter中有賦值等號,因此lambda就沒法實現這個需求了。
__init__
用於在對象被實例化時爲對象初始化一些屬性信息,按照其餘語言的邏輯,也能夠稱之爲構造器,既然有構造器,那麼就會有析構器,即在對象被銷燬時執行。在Python的類中使用__del__方法定義的函數稱爲析構函數,在對象被銷燬時觸發執行。須要注意的是,這個方法不能引發對象自己的銷燬,只是對象銷燬的時候會自動調用它。換言之,就是當對象引用計數爲0時,觸發銷燬操做(標記爲可回收,等待GC),而銷燬操做又會觸發對象的__del__方法。
class Person: def __init__(self, name): self.name = name def __del__(self): print('{} is die'.format(self.name)) daxin = Person('daxin') daxin.__del__() # 僅僅是執行__del__方法,不會真正銷燬 daxin.__del__() # 僅僅是執行__del__方法,不會真正銷燬 del daxin # 在本例中引用計數爲0,即真正的銷燬 # 代碼執行完畢後也會觸發銷燬操做。
通常在析構函數中清理當前實例中申請的內存空間或者某些對象,作一些資源釋放的工做。
在其餘面向對象的高級語言中,會有重載的改變。所謂重載,就是同一個方法名,可是參數數量、類型不同,就是同一個方法的重載。
下面是一段模擬其餘語言方法重載的僞代碼
:
func test(x int, y int) { pass } func test(x array,y array) { pass } test(4,5) test([1],[2])
在其餘語言中,當test(4,5)時會調用上面的函數,當test([1],[2])會調用下面的函數,這就叫作類型重載,傳遞不一樣的參數類型,就會執行對應的方法,而在Python中,後面的會直接覆蓋前面的同名函數,因此Python沒有重載,固然Python也不須要重載,爲何呢?由於Python的動態語言的特性,其實悄悄的就實現了其餘語言的類型重載
def test(x,y): return x + y test(4,5) test([1],[2]) test('a','b')
在Python中上面是能夠執行的,可是在其餘語言中,可能就須要對應三個函數處理不一樣類型的數據,而後經過類型重載來調用。因爲Python語言的特性,天生就能實現類型重載的功能。