Python之路--Python基礎7--面向對象

1、初識面向對象

面向過程:java

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

  優勢:極大地下降了寫成學的複雜度,只須要順着執行的步驟,堆疊代碼便可linux

  缺點:一套流水線或者流程就是用來解決一個問題,若是修改代碼就都得改變git

 

面向對象:上帝的思想程序員

  優勢:解決了程序的擴展性。對某一個對象單獨修改,會馬上反映到整個體系中,如對遊戲中一我的物參數的特徵和技能修改都很容易。github

  缺點:可控性差,沒法向面向過程的程序設計流水線式的能夠很精準的預測問題的處理流程與結果,面向對象的程序一旦開始就由對象之間的交互解決問題,即使是上帝也沒法預測最終結果。因而咱們常常看到一個遊戲人某一參數的修改極有可能致使陰霸的技能出現,一刀砍死3我的,這個遊戲就失去平衡。算法

 

Class 類編程

  一個類便是對一類擁有相同屬性的對象的抽象、藍圖、原型。在類中定義了這些對象的都具有的屬性(variables(data))、共同的方法設計模式

 

Object 對象 安全

  一個對象便是一個類的實例化後實例,一個類必須通過實例化後方可在程序中調用,一個類能夠實例化多個對象,每一個對象亦能夠有不一樣的屬性,就像人類是指全部人,每一個人是指具體的對象,人與人以前有共性,亦有不一樣

 

Encapsulation 封裝

  在類中對數據的賦值、內部調用對外部用戶是透明的,這使類變成了一個膠囊或容器,裏面包含着類的數據和方法

 

Inheritance 繼承

  一個類能夠派生出子類,在這個父類裏定義的屬性、方法自動被子類繼承

 

Polymorphism 多態

  多態是面向對象的重要特性,簡單點說:「一個接口,多種實現」,指一個基類中派生出了不一樣的子類,且每一個子類在繼承了一樣的方法名的同時又對父類的方法作了不一樣的實現,這就是同一種事物表現出的多種形態。

  編程其實就是一個將具體世界進行抽象化的過程,多態就是抽象化的一種體現,把一系列具體事物的共同點抽象出來, 再經過這個抽象的事物, 與不一樣的具體事物進行對話。對不一樣類的對象發出相同的消息將會有不一樣的行爲。好比,你的老闆讓全部員工在九點鐘開始工做, 他只要在九點鐘的時候說:「開始工做」便可,而不須要對銷售人員說:「開始銷售工做」,對技術人員說:「開始技術工做」, 由於「員工」是一個抽象的事物, 只要是員工就能夠開始工做,他知道這一點就好了。至於每一個員工,固然會各司其職,作各自的工做。多態容許將子類的對象看成父類的對象使用,某父類型的引用指向其子類型的對象,調用的方法是該子類型的方法。這裏引用和調用方法的代碼編譯前就已經決定了,而引用所指向的對象能夠在運行期間動態綁定。

 

2、面向對象編程介紹

  對於編程語言的初學者來說,OOP不是一個很容易理解的編程方式,你們雖然都按老師講的都知道OOP的三大特性是繼承、封裝、多態,而且你們也都知道了如何定義類、方法等面向對象的經常使用語法,可是一到真正寫程序的時候,仍是不少人喜歡用函數式編程來寫代碼,特別是初學者,很容易陷入一個窘境就是「我知道面向對象,我也會寫類,但我依然沒發如今使用了面向對象後,對咱們的程序開發效率或其它方面帶來什麼好處,由於我使用函數編程就能夠減小重複代碼並作到程序可擴展了,爲啥子還用面向對象?」。 對於此,我我的以爲緣由應該仍是由於你沒有充分了解到面向對象能帶來的好處,今天我就寫一篇關於面向對象的入門文章,但願能幫你們更好的理解和使用面向對象編程。

 

不管用什麼形式來編程,咱們都要明確記住如下原則:

一、寫重複代碼是很是很差的低級行爲

二、你寫的代碼須要常常變動

 

  開發正規的程序跟那種寫個運行一次就扔了的小腳本一個很大不一樣就是,你的代碼老是須要不斷的更改,不是修改bug就是添加新功能等,因此爲了往後方便程序的修改及擴展,你寫的代碼必定要遵循易讀、易改的原則(專業數據叫可讀性好、易擴展)。

  若是你把一段一樣的代碼複製、粘貼到了程序的多個地方以實如今程序的各個地方調用 這個功能,那往後你再對這個功能進行修改時,就須要把程序裏多個地方都改一遍,這種寫程序的方式是有問題的,由於若是你不當心漏掉了一個地方沒改,那可能會致使整個程序的運行都 出問題。 所以咱們知道 在開發中必定要努力避免寫重複的代碼,不然就至關於給本身再挖坑。

   還好,函數的出現就能幫咱們輕鬆的解決重複代碼的問題,對於須要重複調用的功能,只須要把它寫成一個函數,而後在程序的各個地方直接調用這個函數名就行了,而且當須要修改這個功能時,只需改函數代碼,而後整個程序就都更新了。

  其實OOP編程的主要做用也是使你的代碼修改和擴展變的更容易,那麼小白要問了,既然函數都能實現這個需求了,還要OOP幹毛線用呢? 呵呵,說這話就像,古時候,人們打仗殺人都用刀,後來出來了槍,它的主要功能跟刀同樣,也是殺人,而後小白就問,既然刀能殺人了,那還要槍幹毛線,哈哈,顯而易見,由於槍能更好更快更容易的殺人。函數編程與OOP的主要區別就是OOP可使程序更加容易擴展和易更改。

 

類的語法:

栗子:

class Dog(object): def __init__(self,name,dog_type): self.name = name self.type = dog_type def sayhi(self): print("hello,I am a dog, my name is ",self.name) d = Dog('A',"京巴")  #實例化Dog類,生成一個d對象
d.sayhi()  # #

如下爲Dog實例化的流程圖:

根據上圖咱們得知,其實self,就是實例自己!你實例化時python會自動把這個實例自己經過self參數傳進去。

剛纔定義的這個類體現了面向對象的第一個基本特性,封裝,其實就是使用構造方法將內容封裝到某個具體對象中,而後經過對象直接或者self間接獲取被封裝的內容

 

3、繼承與派生

一、初識繼承

繼承是一種建立新類的方式,新建的類能夠繼承一個或多個父類(python支持多繼承),父類又可稱爲基類或超類,新建的類稱爲派生類或子類。

子類會「」遺傳」父類的屬性,從而解決代碼重用問題

python中類的繼承分爲:單繼承和多繼承

class ParentClass1: #定義父類
    pass

class ParentClass2: #定義父類
    pass

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

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

 

查看繼承

>>> SubClass1.__bases__ #__base__只查看從左到右繼承的第一個子類,__bases__則是查看全部繼承的父類
(<class '__main__.ParentClass1'>,) >>> SubClass2.__bases__ (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)

 

經典類與新式類

1.只有在python2中才分新式類和經典類,python3中統一都是新式類

2.在python2中,沒有顯式的繼承object類的類,以及該類的子類,都是經典類

3.在python2中,顯式地聲明繼承object的類,以及該類的子類,都是新式類

4.在python3中,不管是否繼承object,都默認繼承object,即python3中全部類均爲新式類

#關於新式類與經典類的區別,咱們稍後討論

 

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

>>> ParentClass1.__bases__ (<class 'object'>,) >>> ParentClass2.__bases__ (<class 'object'>,)

 

二、繼承與抽象(先抽象在繼承)

繼承描述的是子類與父類之間的關係,是一種什麼是什麼的關係。要找出這種關係,必須先抽象再繼承。

抽象即抽取相似或者說比較像的部分。

抽象分紅兩個層次: 

1.將奧巴馬和梅西這倆對象比較像的部分抽取成類; 

2.將人,豬,狗這三個類比較像的部分抽取成父類。

抽象最主要的做用是劃分類別(能夠隔離關注點,下降複雜度)

 

繼承:是基於抽象的結果,經過編程語言去實現它,確定是先經歷抽象這個過程,才能經過繼承的方式去表達出抽象的結構。

抽象只是分析和設計的過程當中,一個動做或者說一種技巧,經過抽象能夠獲得類

 

三、繼承與重用性

在開發程序的過程當中,若是咱們定義了一個類A,而後又想新創建另一個類B,可是類B的大部份內容與類A的相同時,咱們不可能從頭開始寫一個類B,這就用到了類的繼承的概念。經過繼承的方式新建類B,讓B繼承A,B會‘遺傳’A的全部屬性(數據屬性和函數屬性),實現代碼重用。

代碼示慄:

class Hero: def __init__(self,nickname,aggressivity,life_value): self.nickname=nickname self.aggressivity=aggressivity self.life_value=life_value def move_forward(self): print('%s move forward' %self.nickname) def move_backward(self): print('%s move backward' %self.nickname) def move_left(self): print('%s move forward' %self.nickname) def move_right(self): print('%s move forward' %self.nickname) def attack(self,enemy): enemy.life_value-=self.aggressivity class Garen(Hero): pass

class Riven(Hero): pass g1=Garen('草叢倫',100,300) r1=Riven('銳雯雯',57,200) print(g1.life_value) r1.attack(g1) print(g1.life_value) 
''' 300
243
'''

 

四、派生

  固然子類也能夠添加本身新的屬性或者在本身這裏從新定義這些屬性(不會影響到父類),須要注意的是,一旦從新定義了本身的屬性且與父類重名,那麼調用新增的屬性時,就以本身爲準了。

  在子類中,新建的重名的函數屬性,在編輯函數內功能的時候,有可能須要重用父類中重名的那個函數功能,應該是用調用普通函數的方式,即:類名.func(),此時就與調用普通函數無異了,所以即使是self參數也要爲其傳值。

class Riven(Hero): camp='Noxus'
    def __init__(self,nickname,aggressivity,life_value,skin): Hero.__init__(self,nickname,aggressivity,life_value) #調用父類功能
        self.skin=skin #新屬性

def attack(self,enemy): #在本身這裏定義新的attack,再也不使用父類的attack,且不會影響父類 Hero.attack(self,enemy) #調用功能 print('from riven') def fly(self): #在本身這裏定義新的 print('%s is flying' %self.nickname) r1=Riven('銳雯雯',57,200,'比基尼') r1.fly() print(r1.skin) ''' 運行結果 銳雯雯 is flying 比基尼 '''

 

五、組合與重用性

軟件重用的重要方式除了繼承以外還有另一種方式,即:組合。

組合指的是,在一個類中以另一個類的對象做爲數據屬性,稱爲類的組合。

class Equip: #武器裝備類 def fire(self): print('release Fire skill') class Riven: #英雄Riven的類,一個英雄須要有裝備,於是須要組合Equip類 camp='Noxus' def __init__(self,nickname): self.nickname=nickname self.equip=Equip() #用Equip類產生一個裝備,賦值給實例的equip屬性  r1=Riven('銳雯雯') r1.equip.fire() #可使用組合的類產生的對象所持有的方法 #release Fire skill  

 

組合與繼承都是有效地利用已有類的資源的重要方式。可是兩者的概念和使用場景皆不一樣,

1.繼承的方式

經過繼承創建了派生類與基類之間的關係,它是一種'是'的關係,好比白馬是馬,人是動物。

當類之間有不少相同的功能,提取這些共同的功能作成基類,用繼承比較好,好比老師是人,學生是人

2.組合的方式

用組合的方式創建了類與組合的類之間的關係,它是一種‘有’的關係,好比教授有生日,教授教python和linux課程,教授有學生s一、s二、s3...

栗子:

class People: def __init__(self,name,age,sex): self.name=name self.age=age self.sex=sex class Course: def __init__(self,name,period,price): self.name=name self.period=period self.price=price def tell_info(self): print('<%s %s %s>' %(self.name,self.period,self.price)) class Teacher(People): def __init__(self,name,age,sex,job_title): People.__init__(self,name,age,sex) self.job_title=job_title self.course=[] self.students=[] class Student(People): def __init__(self,name,age,sex): People.__init__(self,name,age,sex) self.course=[] def tell_info(self): print('<%s %s %s>' % (self.name,self.age,self.sex)) egon=Teacher('egon',18,'male','NB') s1=Student('牛榴彈',18,'female') python=Course('python','3mons',3000.0) linux=Course('linux','3mons',3000.0) #爲老師egon和學生s1添加課程
egon.course.append(python)  #往課程列表裏面添加的是課程對象
egon.course.append(linux) s1.course.append(python) #爲老師egon添加學生s1
egon.students.append(s1)    #往學生列表裏面添加的是學生對象


#使用
for obj in egon.course: obj.tell_info() #調用課程對象的tell_info方法打印課程信息

for stu in egon.students: stu.tell_info() #調用學生對象的tell_info方法打印學生信息


''' <python 3mons 3000.0> <linux 3mons 3000.0> <牛榴彈 18 female> '''

 

六、接口與歸一化設計

接口提取了一羣類共同的函數,能夠把接口當作一個函數的集合。

而後讓子類去實現接口中的函數。

這麼作的意義在於歸一化,什麼叫歸一化,就是隻要是基於同一個接口實現的類,那麼全部的這些類產生的對象在使用時,從用法上來講都同樣。

 

歸一化的好處在於:

1. 歸一化讓使用者無需關心對象的類是什麼,只須要的知道這些對象都具有某些功能就能夠了,這極大地下降了使用者的使用難度。

2. 歸一化使得高層的外部使用者能夠不加區分的處理全部接口兼容的對象集合

  2.1:就好象linux的泛文件概念同樣,全部東西均可以當文件處理,沒必要關心它是內存、磁盤、網絡仍是屏幕(固然,對底層設計者,固然也能夠區分出「字符設備」和「塊設備」,而後作出針對性的設計:細緻到什麼程度,視需求而定)。

  2.2:再好比:咱們有一個汽車接口,裏面定義了汽車全部的功能,而後由本田汽車的類,奧迪汽車的類,大衆汽車的類,他們都實現了汽車接口,這樣就好辦了,你們只須要學會了怎麼開汽車,那麼不管是本田,仍是奧迪,仍是大衆咱們都會開了,開的時候根本無需關心我開的是哪一類車,操做手法(函數調用)都同樣。

 

模仿interface

在python中根本就沒有一個叫作interface的關鍵字,若是非要去模仿接口的概念

一、能夠藉助第三方模塊:

http://pypi.python.org/pypi/zope.interface

twisted的twisted\internet\interface.py裏使用zope.interface

文檔https://zopeinterface.readthedocs.io/en/latest/

設計模式:https://github.com/faif/python-patterns

 

二、也可使用繼承: 

繼承的兩種用途

一:繼承基類的方法,而且作出本身的改變或者擴展(代碼重用):實踐中,繼承的這種用途意義並不很大,甚至經常是有害的。由於它使得子類與基類出現強耦合。

二:聲明某個子類兼容於某基類,定義一個接口類(模仿java的Interface),接口類中定義了一些接口名(就是函數名)且並未實現接口的功能,子類繼承接口類,而且實現接口中的功能。

class Interface:#定義接口Interface類來模仿接口的概念,python中壓根就沒有interface關鍵字來定義一個接口。
    def read(self): #定接口函數read
        pass

    def write(self): #定義接口函數write
        pass


class Txt(Interface): #文本,具體實現read和write
    def read(self): print('文本數據的讀取方法') def write(self): print('文本數據的讀取方法') class Sata(Interface): #磁盤,具體實現read和write
    def read(self): print('硬盤數據的讀取方法') def write(self): print('硬盤數據的讀取方法') class Process(Interface): def read(self): print('進程數據的讀取方法') def write(self): print('進程數據的讀取方法')

上面的代碼只是看起來像接口,其實並無起到接口的做用,子類徹底能夠不用去實現接口 ,這就用到了抽象類。

 

七、抽象類

7.一、什麼是抽象類

    與java同樣,python也有抽象類的概念可是一樣須要藉助模塊實現,抽象類是一個特殊的類,它的特殊之處在於只能被繼承,不能被實例化。

 

7.二、爲何要有抽象類

    若是說類是從一堆對象中抽取相同的內容而來的,那麼抽象類就是從一堆類中抽取相同的內容而來的,內容包括數據屬性和函數屬性。

  好比咱們有香蕉的類,有蘋果的類,有桃子的類,從這些類抽取相同的內容就是水果這個抽象的類,你吃水果時,要麼是吃一個具體的香蕉,要麼是吃一個具體的桃子。。。。。。你永遠沒法吃到一個叫作水果的東西。

    從設計角度去看,若是類是從現實對象抽象而來的,那麼抽象類就是基於類抽象而來的。

  從實現角度來看,抽象類與普通類的不一樣之處在於:抽象類中只能有抽象方法(沒有實現功能),該類不能被實例化,只能被繼承,且子類必須實現抽象方法。這一點與接口有點相似,但實際上是不一樣的,即將揭曉答案

 

7.三、在python中實現抽象類

#_*_coding:utf-8_*_ #一切皆文件
import abc #利用abc模塊實現抽象類

class All_file(metaclass=abc.ABCMeta): all_type='file' @abc.abstractmethod #定義抽象方法,無需實現功能,裝飾器
    def read(self): '子類必須定義讀功能'
        pass @abc.abstractmethod #定義抽象方法,無需實現功能
    def write(self): '子類必須定義寫功能'
        pass

# class Txt(All_file): # pass # # t1=Txt() #這樣會報錯,子類沒有定義抽象方法

class Txt(All_file): #子類繼承抽象類,可是必須定義read和write方法
    def read(self): print('文本數據的讀取方法') def write(self): print('文本數據的讀取方法') class Sata(All_file): #子類繼承抽象類,可是必須定義read和write方法
    def read(self): print('硬盤數據的讀取方法') def write(self): print('硬盤數據的讀取方法') class Process(All_file): #子類繼承抽象類,可是必須定義read和write方法
    def read(self): print('進程數據的讀取方法') def write(self): print('進程數據的讀取方法') txt_file=Txt() sata_file=Sata() process_file=Process() 
#這樣你們都是被歸一化了,也就是一切皆文件的思想 txt_file.read() sata_file.write() process_file.read() print(txt_file.all_type) print(sata_file.all_type) print(process_file.all_type)

 

7.四、抽象類與接口

抽象類的本質仍是類,指的是一組類的類似性,包括數據屬性(如all_type)和函數屬性(如read、write),而接口只強調函數屬性的類似性。

抽象類是一個介於類和接口直接的一個概念,同時具有類和接口的部分特性,能夠用來實現歸一化設計 。

 

八、繼承實現的原理

8.一、繼承順序

在Java和C#中子類只能繼承一個父類,而Python中子類能夠同時繼承多個父類,如A(B,C,D)。

若是繼承關係爲非菱形結構,則會按照先找B這一條分支,而後再找C這一條分支,最後找D這一條分支的順序直到找到咱們想要的屬性

若是繼承關係爲菱形結構,那麼屬性的查找方式有兩種,分別是:深度優先和廣度

 

class A(object): def test(self): print('from A') class B(A): def test(self): print('from B') class C(A): def test(self): print('from C') class D(B): def test(self): print('from D') class E(C): def test(self): print('from E') class F(D,E): # def test(self):
    # print('from F')
    pass f1=F() f1.test() print(F.__mro__) #只有新式纔有這個屬性能夠查看線性列表,經典類沒有這個屬性

#新式類繼承順序:F->D->B->E->C->A #經典類繼承順序:F->D->B->A->E->C #python3中統一都是新式類 #pyhon2中才分新式類與經典類

 

8.二、繼承原理

python究竟是如何實現繼承的,對於你定義的每個類,python會計算出一個方法解析順序(MRO)列表,這個MRO列表就是一個簡單的全部基類的線性順序列表,例如

>>> F.mro() #等同於F.__mro__
[<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

爲了實現繼承,python會在MRO列表上從左到右開始查找基類,直到找到第一個匹配這個屬性的類爲止。
而這個MRO列表的構造是經過一個C3線性化算法來實現的。咱們不去深究這個算法的數學原理,它實際上就是合併全部父類的MRO列表並遵循以下三條準則:
1.子類會先於父類被檢查
2.多個父類會根據它們在列表中的順序被檢查
3.若是對下一個類存在兩個合法的選擇,選擇第一個父類

 

九、子類中調用父類的方法

方法一:指名道姓,即父類名.父類方法()

#_*_coding:utf-8_*_

class Vehicle: #定義交通工具類
     Country='China'
     def __init__(self,name,speed,load,power): self.name=name self.speed=speed self.load=load self.power=power def run(self): print('開動啦...') class Subway(Vehicle): #地鐵
    def __init__(self,name,speed,load,power,line): Vehicle.__init__(self,name,speed,load,power) self.line=line def run(self): print('地鐵%s號線歡迎您' %self.line)  Vehicle.run(self) line13=Subway('中國地鐵','180m/s','1000人/箱','',13) line13.run()

 

方法二:super()

class Vehicle: #定義交通工具類
     Country='China'
     def __init__(self,name,speed,load,power): self.name=name self.speed=speed self.load=load self.power=power def run(self): print('開動啦...') class Subway(Vehicle): #地鐵
    def __init__(self,name,speed,load,power,line): #super(Subway,self) 就至關於實例自己 在python3中super()等同於super(Subway,self)
        super().__init__(name,speed,load,power) self.line=line def run(self): print('地鐵%s號線歡迎您' %self.line) super(Subway,self).run()  line13=Subway('中國地鐵','180m/s','1000人/箱','',13) line13.run()

強調:兩者使用哪種均可以,但最好不要混合使用 

 

指名道姓與super()的區別:

#指名道姓
class A: def __init__(self): print('A的構造方法') class B(A): def __init__(self): print('B的構造方法') A.__init__(self) class C(A): def __init__(self): print('C的構造方法') A.__init__(self) class D(B,C): def __init__(self): print('D的構造方法') B.__init__(self) C.__init__(self)

f1=D() #A.__init__被重複調用 ''' D的構造方法 B的構造方法 A的構造方法 C的構造方法 A的構造方法 ''' #使用super() class A: def __init__(self): print('A的構造方法') class B(A): def __init__(self): print('B的構造方法') super(B,self).__init__() class C(A): def __init__(self): print('C的構造方法') super(C,self).__init__() class D(B,C): def __init__(self): print('D的構造方法') super(D,self).__init__() f1=D() #super()會基於mro列表,日後找 ''' D的構造方法 B的構造方法 C的構造方法 A的構造方法 '''

  當你使用super()函數時,Python會在MRO列表上繼續搜索下一個類。只要每一個重定義的方法統一使用super()並只調用它一次,那麼控制流最終會遍歷完整個MRO列表,每一個方法也只會被調用一次(注意注意注意:使用super調用的全部屬性,都是從MRO列表當前的位置日後找,千萬不要經過看代碼去找繼承關係,必定要看MRO列表)

 

4、多態與多態性

一、多態

多態指的是一類事物有多種形態

動物有多種形態:人,狗,豬

import abc class Animal(metaclass=abc.ABCMeta): #同一類事物:動物
 @abc.abstractmethod def talk(self): pass

class People(Animal): #動物的形態之一:人
    def talk(self): print('say hello') class Dog(Animal): #動物的形態之二:狗
    def talk(self): print('say wangwang') class Pig(Animal): #動物的形態之三:豬
    def talk(self): print('say aoao')

 

文件有多種形態:文本文件,可執行文件

import abc class File(metaclass=abc.ABCMeta): #同一類事物:文件
 @abc.abstractmethod def click(self): pass

class Text(File): #文件的形態之一:文本文件
    def click(self): print('open file') class ExeFile(File): #文件的形態之二:可執行文件
    def click(self): print('execute file')

 

二、多態性

peo=People() dog=Dog() pig=Pig() #peo、dog、pig都是動物,只要是動物確定有talk方法 #因而咱們能夠不用考慮它們三者的具體是什麼類型,而直接使用
peo.talk() dog.talk() pig.talk() #更進一步,咱們能夠定義一個統一的接口來使用
def func(obj): obj.talk()

 

5、封裝

在python中用雙下劃線開頭的方式將屬性隱藏起來(設置成私有的)

#其實這僅僅這是一種變形操做且僅僅只在類定義階段發生變形 #類中全部雙下劃線開頭的名稱如__x都會在類定義時自動變造成:_類名__x的形式:

class A: __N=0 #類的數據屬性就應該是共享的,可是語法上是能夠把類的數據屬性設置成私有的如__N,會變形爲_A__N
    def __init__(self): self.__X=10 #變形爲self._A__X
    def __foo(self): #變形爲_A__foo
        print('from A') def bar(self): self.__foo() #只有在類內部才能夠經過__foo的形式訪問到.

#A._A__N是能夠訪問到的, #這種,在外部是沒法經過__x這個名字訪問到。

這種變形須要注意的問題是:

  1.這種機制也並無真正意義上限制咱們從外部直接訪問屬性,知道了類名和屬性名就能夠拼出名字:_類名__屬性,而後就能夠訪問了,如a._A__N,即這種操做並非嚴格意義上的限制外部訪問,僅僅只是一種語法意義上的變形,主要用來限制外部的直接訪問。

  2.變形的過程只在類的定義時發生一次,在定義後的賦值操做,不會變形。

  3.在繼承中,父類若是不想讓子類覆蓋本身的方法,能夠將方法定義爲私有的。

#正常狀況
>>> class A: ... def fa(self): ... print('from A') ... def test(self): ... self.fa() ... >>> class B(A): ... def fa(self): ... print('from B') ... >>> b=B() >>> b.test() from B #把fa定義成私有的,即__fa
>>> class A: ... def __fa(self): #在定義時就變形爲_A__fa
...         print('from A') ... def test(self): ... self.__fa() #只會與本身所在的類爲準,即調用_A__fa
... >>> class B(A): ... def __fa(self): ... print('from B') ... >>> b=B() >>> b.test() from A

 

  封裝的真諦在於明確地區份內外,封裝的屬性能夠直接在內部使用,而不能被外部直接使用,然而定義屬性的目的終歸是要用,外部要想用類隱藏的屬性,須要咱們爲其開闢接口,讓外部可以間接地用到咱們隱藏起來的屬性,那這麼作的意義何在???

  1:封裝數據:將數據隱藏起來這不是目的。隱藏起來而後對外提供操做該數據的接口,而後咱們能夠在接口附加上對該數據操做的限制,以此完成對數據屬性操做的嚴格控制。

class Teacher: def __init__(self,name,age): # self.__name=name
        # self.__age=age
 self.set_info(name,age) def tell_info(self): print('姓名:%s,年齡:%s' %(self.__name,self.__age)) def set_info(self,name,age): if not isinstance(name,str): raise TypeError('姓名必須是字符串類型') if not isinstance(age,int): raise TypeError('年齡必須是整型') self.__name=name self.__age=age t=Teacher('egon',18) t.tell_info() t.set_info('egon',19) t.tell_info()

  2:封裝方法:目的是隔離複雜度

  封裝方法舉例: 

    1. 電視機自己是一個黑盒子,隱藏了全部細節,可是必定會對外提供了一堆按鈕,這些按鈕也正是接口的概念,因此說,封裝並非單純意義的隱  藏!!!

    2. 快門就是傻瓜相機爲傻瓜們提供的方法,該方法將內部複雜的照相功能都隱藏起來了

提示:在編程語言裏,對外提供的接口(接口可理解爲了一個入口),能夠是函數,稱爲接口函數,這與接口的概念還不同,接口表明一組接口函數的集合體。

#取款是功能,而這個功能有不少功能組成:插卡、密碼認證、輸入金額、打印帳單、取錢 #對使用者來講,只須要知道取款這個功能便可,其他功能咱們均可以隱藏起來,很明顯這麼作 #隔離了複雜度,同時也提高了安全性

class ATM: def __card(self): print('插卡') def __auth(self): print('用戶認證') def __input(self): print('輸入取款金額') def __print_bill(self): print('打印帳單') def __take_money(self): print('取款') def withdraw(self): self.__card() self.__auth() self.__input() self.__print_bill() self.__take_money() a=ATM() a.withdraw()

  python並不會真的阻止你訪問私有的屬性,模塊也遵循這種約定,若是模塊名以單下劃線開頭,那麼from module import *時不能被導入,可是你from module import _private_module依然是能夠導入的。其實不少時候你去調用一個模塊的功能時會遇到單下劃線開頭的(socket._socket,sys._home,sys._clear_type_cache),這些都是私有的,原則上是供內部調用的,做爲外部的你,獨斷獨行也是能夠用的,只不過顯得稍微傻逼一點點,python要想與其餘編程語言同樣,嚴格控制屬性的訪問權限,只能藉助內置方法如__getattr__,詳見面向對象進階。

 

  property是一種特殊的屬性,訪問它時會執行一段功能(函數)而後返回值。

看栗子:

class People: def __init__(self,name,weight,height): self.name=name self.weight=weight self.height=height @property def bmi(self): return self.weight / (self.height**2) p1=People('egon',75,1.85) print(p1.bmi)  #直接把bmi當作一個屬性,進行訪問

 

栗子二:圓的周長和麪積 :

import math class Circle: def __init__(self,radius): #圓的半徑radius
        self.radius=radius @property def area(self): return math.pi * self.radius**2 #計算面積
 @property def perimeter(self): return 2*math.pi*self.radius #計算周長
 c=Circle(10) print(c.radius) print(c.area) #能夠向訪問數據屬性同樣去訪問area,會觸發一個函數的執行,動態計算出一個值
print(c.perimeter) #同上
''' 輸出結果:
10 314.1592653589793 62.83185307179586
'''
#注意:此時的特性arear和perimeter不能被賦值
c.area=3 #爲特性area賦值
''' 拋出異常: AttributeError: can't set attribute '''
class Foo: def __init__(self,val): self.__NAME=val #將全部的數據屬性都隱藏起來
 @property def name(self): return self.__NAME #obj.name訪問的是self.__NAME(這也是真實值的存放位置)
 @name.setter def name(self,value): if not isinstance(value,str):  #在設定值以前進行類型檢查
            raise TypeError('%s must be str' %value) self.__NAME=value #經過類型檢查後,將值value存放到真實的位置self.__NAME
 @name.deleter def name(self): raise TypeError('Can not delete') f=Foo('egon') print(f.name) # f.name=10 #拋出異常'TypeError: 10 must be str'
del f.name #拋出異常'TypeError: Can not delete'

  封裝在於明確區份內外,使得類實現者能夠修改封裝內的東西而不影響外部調用者的代碼;而外部使用用者只知道一個接口(函數),只要接口(函數)名、參數不變,使用者的代碼永遠無需改變。這就提供一個良好的合做基礎——或者說,只要接口這個基礎約定不變,則代碼改變不足爲慮。

#類的設計者
class Room: def __init__(self,name,owner,width,length,high): self.name=name self.owner=owner self.__width=width self.__length=length self.__high=high def tell_area(self): #對外提供的接口,隱藏了內部的實現細節,此時咱們想求的是面積
        return self.__width * self.__length

#使用者
>>> r1=Room('臥室','egon',20,20,20) >>> r1.tell_area() #使用者調用接口tell_area
400

#類的設計者,輕鬆的擴展了功能,而類的使用者徹底不須要改變本身的代碼
class Room: def __init__(self,name,owner,width,length,high): self.name=name self.owner=owner self.__width=width self.__length=length self.__high=high def tell_area(self): #對外提供的接口,隱藏內部實現,此時咱們想求的是體積,內部邏輯變了,只需求修該下列一行就能夠很簡答的實現,並且外部調用感知不到,仍然使用該方法,可是功能已經變了
        return self.__width * self.__length * self.__high


#對於仍然在使用tell_area接口的人來講,根本無需改動本身的代碼,就能夠用上新功能
>>> r1.tell_area() 8000

 

6、Python中關於OOP的經常使用術語

抽象/實現

  抽象指對現實世界問題和實體的本質表現,行爲和特徵建模,創建一個相關的子集,能夠用於繪程序結構,從而實現這種模型。抽象不只包括這種模型的數據屬性,還定義了這些數據的接口。對某種抽象的實現就是對此數據及與之相關接口的現實化(realization)。現實化這個過程對於客戶 程序應當是透明並且無關的。 

 

封裝/接口

  封裝描述了對數據/信息進行隱藏的觀念,它對數據屬性提供接口和訪問函數。經過任何客戶端直接對數據的訪問,無視接口,與封裝性都是背道而馳的,除非程序員容許這些操做。做爲實現的 一部分,客戶端根本就不須要知道在封裝以後,數據屬性是如何組織的。在Python中,全部的類屬性都是公開的,但名字可能被「混淆」了,以阻止未經受權的訪問,但僅此而已,再沒有其餘預防措施了。這就須要在設計時,對數據提供相應的接口,以避免客戶程序經過不規範的操做來存取封裝的數據屬性。

注意:封裝毫不是等於「把不想讓別人看到、之後可能修改的東西用private隱藏起來」

真正的封裝是,通過深刻的思考,作出良好的抽象,給出「完整且最小」的接口,並使得內部細節能夠對外透明

(注意:對外透明的意思是外部調用者能夠順利的獲得本身想要的任何功能,徹底意識不到內部細節的存在)

 

合成

  合成擴充了對類的 述,使得多個不一樣的類合成爲一個大的類,來解決現實問題。合成 述了 一個異常複雜的系統,好比一個類由其它類組成,更小的組件也多是其它的類,數據屬性及行爲, 全部這些合在一塊兒,彼此是「有一個」的關係。

 

派生/繼承/繼承結構

  派生描述了子類衍生出新的特性,新類保留已存類類型中全部須要的數據和行爲,但容許修改或者其它的自定義操做,都不會修改原類的定義。
  繼承描述了子類屬性從祖先類繼承這樣一種方式。
  繼承結構表示多「代」派生,能夠述成一個「族譜」,連續的子類,與祖先類都有關係。

 

泛化/特化  

  基於繼承泛化表示全部子類與其父類及祖先類有同樣的特色。特化描述全部子類的自定義,也就是,什麼屬性讓它與其祖先類不一樣。

 

多態與多態性

  多態指的是同一種事物的多種狀態:水這種事物有多種不一樣的狀態:冰,水蒸氣。

  多態性的概念指出了對象如何經過他們共同的屬性和動做來操做及訪問,而不需考慮他們具體的類。

  冰,水蒸氣,都繼承於水,它們都有一個同名的方法就是變成雲,可是冰.變雲(),與水蒸氣.變雲()是大相徑庭的過程,雖然調用的方法都同樣。

 

自省/反射

  自省也稱做反射,這個性質展現了某對象是如何在運行期取得自身信息的。若是傳一個對象給你,你能夠查出它有什麼能力,這是一項強大的特性。若是Python不支持某種形式的自省功能,dir和type內建函數,將很難正常工做。還有那些特殊屬性,像__dict__,__name__及__doc__

 

7、領域模型

從領域模型開始,咱們就開始了面向對象的分析和設計過程,能夠說,領域模型是完成從需求分析到面向對象設計的一座橋樑。 

領域模型,顧名思義,就是需求所涉及的領域的一個建模,更通俗的講法是業務模型。參考百度百科(http://baike.baidu.cn/view/757895.htm ),領域模型定義以下: 

從這個定義咱們能夠看出,領域模型有兩個主要的做用:

  一、發掘重要的業務領域概念

  二、創建業務領域概念之間的關係 

 

領域建模三字經 

  領域模型如此重要,不少同窗可能會認爲領域建模很複雜,須要很高的技巧。然而事實上領域建模很是簡單,簡單得有點難以讓人相信,領域建模的方法歸納一下就是「找名詞」! 許多同窗看到這個方法後估計都會笑出來:太假了吧,這麼簡單,找個初中生都會啊,那咱們公司那些分 析師和設計師還有什麼用哦?

  分析師和設計師固然有用,後面咱們會看到,即便是簡單的找名詞這樣的操做,也涉及到分析和提煉,而 不是簡單的摘取出來就可,這種狀況下分析師和設計師的經驗和技能就可以派上用場了。但領域模型分析也確實相對簡單,即便沒有豐富的經驗和高超的技巧,至少也能完成一個能用的領域模型。 

  雖然咱們說「找名詞」很簡單,但一個關鍵的問題尚未說明:從哪裏找? 若是你還記得領域模型是「需求到面向對象的橋樑」,那麼你確定一會兒就能想到:從需求模型中找,具 體來講就是從用例中找。 

  概括一下域建模的方法就是「從用例中找名詞」。 固然,找到名詞後,爲了可以更加符合面向對象的要求和特色,咱們還須要對這些名詞進一步完善,這就 是接下來的步驟:加屬性,連關係

  最後咱們總結出領域建模的三字經方法:找名詞、加屬性、連關係。 

相關文章
相關標籤/搜索