一、認識面向對象是什麼
面向過程的程序設計的核心就是過程,就是流水線式的思維,過程就是解決問題的步驟,面向過程的設計就好像一條設計好的流水線,考慮周全什麼就處理什麼東西。
優點在於極大地降低了寫程序的複雜度,只需要順着執行的步驟執行就好了。
缺點在於流水線或者流程就是用來解決一個問題,代碼牽一髮而動全身。
應用的場景在於一旦完成很少改變的場景,著名的例子在於Linux內核的開發,以及Apache HTTP Server等。
面向對象的程序設計的核心是對象,要理解對象是什麼東西。面向對象的程序設計好比來設計西遊記。如來要解決的問題就是吧經書傳給東土大唐,如來想了想解決問題需要四個人,唐僧 悟空 豬八戒 沙悟淨 。每個人都有各自的特徵和技能(這就是對象的概念,特徵和技能分別對應對象的屬性和方法),然而這並不好玩,於是如來又安排了一羣妖魔鬼怪,爲了防止師徒四人在路上被搞死,又安排了一羣神仙保駕護航。這些都是對象,然後取經開始,師徒四人與妖魔鬼怪神仙互相纏鬥着直到最後取到真經,如來根本不會管試圖私人按照什麼流程去取經的。
面向對象的程序設計
優點在於解決了程序的擴展性,對某一個對象的單獨修改會立刻反映到整個系統中,如對遊戲中一個任務參數的特徵和技能的修改都變得很容易。
缺點在於可控性很差,無法像面向過程的程序流水線式的可以很精準的預測問題的處理流程和結果。面向對象的程序一旦開始就由對象之間的交互解決問題,即使上帝也不會預測到結果是什麼,於是我們經常看到一個遊戲人某一個參數的修改極有可能導致陰霸的技能出現,一刀砍死三個人,這樣遊戲就失去了平衡。
應用場景在於需求經常變化的軟件,一半需求的變化都集中在用戶層,互聯網應用,企業內部的軟件,遊戲等
在Python中賣你想對象的程序的設計並不是全部。面向對象的便曾可以是程序的維護和擴展變得簡單,並且可以大大提高程序的開發效率。另外,基於面向對象的程序可以使他人更加容易理解你的代碼邏輯。從而是團隊的開發變得更簡單。
瞭解一些名詞:類 對象 實例 實例化
類 具有相同特徵的一類事物(人,狗, 老虎)
對象/實例 具體指某一個事物(隔壁翠花 隔壁老王)
實例化 類——>對象的過程
二、類的相關知識
聲明:
def functionName(args):
'函數文檔字符串'
函數體
'''
class 類名:
'類的文檔字符串'
類體
'''
#我們創建一個類
class Data:
pass
屬性:
class Person: #定義一個人類
role = 'person' #人的角色屬性都是人
def walk(self): #人都可以走路,也就是有一個走路方法
print("person is walking...")
print(Person.role) #查看人的role屬性
print(Person.walk) #引用人的走路方法,注意,這裏不是在調用
實例化:類加括號就是實例化,會自動觸發__init__函數的運行,可以用它來爲每個實例定製自己的特徵
class Person: #定義一個人類
role = 'person' #人的角色屬性都是人
def __init__(self,name):
self.name = name # 每一個角色都有自己的暱稱;
def walk(self): #人都可以走路,也就是有一個走路方法
print("person is walking...")
print(Person.role) #查看人的role屬性
print(Person.walk) #引用人的走路方法,注意,這裏不是在調用
實例化的過程就是類——>對象的過程
原本我們只有一個Person類,在這個過程中,產生了一個egg對象,有自己具體的名字、攻擊力和生命值。
語法:對象名 = 類名(參數)
self
self:在實例化時自動將對象/實例本身傳給__init__的第一個參數,你也可以給他起個別的名字,但是正常人都不會這麼做。
因爲你瞎改別人就不認識。
類屬性的補充
一:我們定義的類的屬性到底存到哪裏了?有兩種方式查看
dir(類名):查出的是一個名字列表
類名.__dict__:查出的是一個字典,key爲屬性名,value爲屬性值
二:特殊的類屬性
類名.__name__# 類的名字(字符串)
類名.__doc__# 類的文檔字符串
類名.__base__# 類的第一個父類(在講繼承時會講)
類名.__bases__# 類所有父類構成的元組(在講繼承時會講)
類名.__dict__# 類的字典屬性
類名.__module__# 類定義所在的模塊
類名.__class__# 實例對應的類(僅新式類中)
三、對象的相關知識
class 類名:
def __init__(self,參數1,參數2):
self.對象的屬性1 = 參數1
self.對象的屬性2 = 參數2
def 方法名(self):pass
def 方法名2(self):pass
對象名 = 類名(1,2) #對象就是實例,代表一個具體的東西
#類名() : 類名+括號就是實例化一個類,相當於調用了__init__方法
#括號裏傳參數,參數不需要傳self,其他與init中的形參一一對應
#結果返回一個對象
對象名.對象的屬性1 #查看對象的屬性,直接用 對象名.屬性名 即可
對象名.方法名() #調用類中的方法,直接用 對象名.方法名() 即可
類名稱空間與對象名稱空間
創建一個類就會創建一個類的名稱空間,用來存儲類中定義的所有名字,這些名字稱爲類的屬性
而類有兩種屬性:靜態屬性和動態屬性
靜態屬性就是直接在類中定義的變量
動態屬性就是定義在類中的方法
其中類的數據屬性是共享給所有對象的
>>>id(egg.role)
4341594072
>>>id(Person.role)
4341594072
而類的動態屬性是綁定到所有類對象的
>>>egg.attack
<bound method Person.attack of <__main__.Person object at 0x101285860>>
>>>Person.attack
<function Person.attack at 0x10127abf8>
創建一個對象/實例就會創建一個對象/實例的名稱空間,存放對象/實例的名字,稱爲對象/實例的屬性
在obj.name會先從obj自己的名稱空間裏找name,找不到則去類中找,類也找不到就找父類…最後都找不到就拋出異常
四、繼承 封裝 多態
繼承是一種創建新類的方式,在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'>)
提示:如果沒有指定基類,python的類會默認繼承object類,object是所有python類的基類,它提供了一些常見方法(如__str__)的實現。
>>> ParentClass1.__bases__
(<class 'object'>,)
>>> ParentClass2.__bases__
(<class 'object'>,)
繼承的重要性
==========================第一部分
例如
貓可以:爬樹、喫、喝、拉、撒
狗可以:看門、喫、喝、拉、撒
如果我們要分別爲貓和狗創建一個類,那麼就需要爲 貓 和 狗 實現他們所有的功能,僞代碼如下:
#貓和狗有大量相同的內容
class 貓:
def爬樹(self):
print '爬樹'
def 喫(self):
# do something
def 喝(self):
# do something
def 拉(self):
# do something
def 撒(self):
# do something
class 狗:
def 看門(self):
print '看門'
def 喫(self):
# do something
def 喝(self):
# do something
def 拉(self):
# do something
def 撒(self):
# do something
==========================第二部分
上述代碼不難看出,喫、喝、拉、撒是貓和狗都具有的功能,而我們卻分別的貓和狗的類中編寫了兩次。如果使用 繼承 的思想,如下實現:
動物:喫、喝、拉、撒
貓:爬樹(貓繼承動物的功能)
狗:看門(狗繼承動物的功能)
僞代碼如下:
class 動物:
def 喫(self):
# do something
def 喝(self):
# do something
def 拉(self):
# do something
def 撒(self):
# do something
# 在類後面括號中寫入另外一個類名,表示當前類繼承另外一個類
class 貓(動物):
def爬樹(self):
print '爬樹'
# 在類後面括號中寫入另外一個類名,表示當前類繼承另外一個類
class 狗(動物):
def 看門(self):
print '看門'
==========================第三部分
#繼承的代碼實現
class Animal:
def eat(self):
print("%s 喫 " %self.name)
def drink(self):
print ("%s 喝 " %self.name)
def shit(self):
print ("%s 拉 " %self.name)
def pee(self):
print ("%s 撒 " %self.name)
class Cat(Animal):
def __init__(self, name):
self.name = name
self.breed = '貓'
def爬樹(self):
print '爬樹'
class Dog(Animal):
def __init__(self, name):
self.name = name
self.breed='狗'
def 看門(self):
print '看門'
# ######### 執行 #########
c1 = Cat('小白家的小黑貓')
c1.eat()
c2 = Cat('小黑的小白貓')
c2.drink()
d1 = Dog('胖子家的小瘦狗')
d1.eat()
使用繼承來重用代碼比較好的例子
那麼問題又來了,多繼承呢?
是否可以繼承多個類
如果繼承的多個類每個類中都定了相同的函數,那麼那一個會被使用呢?
1、Python的類可以繼承多個類,Java和C#中則只能繼承一個類
2、Python的類如果繼承了多個類,那麼其尋找方法的方式有兩種,分別是:深度優先和廣度優先。
當類是經典類時,多繼承情況下,會按照深度優先方式查找
當類是新式類時,多繼承情況下,會按照廣度優先方式查找
經典類和新式類,從字面上可以看出一個老一個新,新的必然包含了跟多的功能,也是之後推薦的寫法,從寫法上區分的話,如果 當前類或者父類繼承了object類,那麼該類便是新式類,否則便是經典類。
class D:
def bar(self):
print 'D.bar'
class C(D):
def bar(self):
print 'C.bar'
class B(D):
def bar(self):
print 'B.bar'
class A(B, C):
def bar(self):
print 'A.bar'
a = A()
# 執行bar方法時
# 首先去A類中查找,如果A類中沒有,則繼續去B類中找,如果B類中麼有,則繼續去D類中找,如果D類中麼有,則繼續去C類中找,如果還是未找到,則報錯
# 所以,查找順序:A --> B --> D --> C
# 在上述查找bar方法的過程中,一旦找到,則尋找過程立即中斷,便不會再繼續找了
a.bar()
經典類多繼承
class D(object):
def bar(self):
print 'D.bar'
class C(D):
def bar(self):
print 'C.bar'
class B(D):
def bar(self):
print 'B.bar'
class A(B, C):
def bar(self):
print 'A.bar'
a = A()
# 執行bar方法時
# 首先去A類中查找,如果A類中沒有,則繼續去B類中找,如果B類中麼有,則繼續去C類中找,如果C類中麼有,則繼續去D類中找,如果還是未找到,則報錯
# 所以,查找順序:A --> B --> C --> D
# 在上述查找bar方法的過程中,一旦找到,則尋找過程立即中斷,便不會再繼續找了
a.bar()
新式類多繼承
經典類:首先去A類中查找,如果A類中沒有,則繼續去B類中找,如果B類中麼有,則繼續去D類中找,如果D類中麼有,則繼續去C類中找,如果還是未找到,則報錯
新式類:首先去A類中查找,如果A類中沒有,則繼續去B類中找,如果B類中麼有,則繼續去C類中找,如果C類中麼有,則繼續去D類中找,如果還是未找到,則報錯
注意:在上述查找過程中,一旦找到,則尋找過程立即中斷,便不會再繼續找了
抽象類與接口類
接口類
繼承有兩種用途:
一:繼承基類的方法,並且做出自己的改變或者擴展(代碼重用)
二:聲明某個子類兼容於某基類,定義一個接口類Interface,接口類中定義了一些接口名(就是函數名)且並未實現接口的功能,子類繼承接口類,並且實現接口中的功能
class Alipay:
'''
支付寶支付
'''
def pay(self,money):
print('支付寶支付了%s元'%money)
class Applepay:
'''
apple pay支付
'''
def pay(self,money):
print('apple pay支付了%s元'%money)
def pay(payment,money):
'''
支付函數,總體負責支付
對應支付的對象和要支付的金額
'''
payment.pay(money)
p = Alipay()
pay(p,200)
開發中容易出現的問題:
class Alipay:
'''
支付寶支付
'''
def pay(self,money):
print('支付寶支付了%s元'%money)
class Applepay:
'''
apple pay支付
'''
def pay(self,money):
print('apple pay支付了%s元'%money)
class Wechatpay:
def fuqian(self,money):
'''
實現了pay的功能,但是名字不一樣
'''
print('微信支付了%s元'%money)
def pay(payment,money):
'''
支付函數,總體負責支付
對應支付的對象和要支付的金額
'''
payment.pay(money)
p = Wechatpay()
pay(p,200) #執行會報錯
接口初成:手動報異常:NotImplementedError來解決開發中遇到的問題
class Payment:
def pay(self):
raise NotImplementedError
class Wechatpay(Payment):
def fuqian(self,money):
print('微信支付了%s元'%money)
p = Wechatpay() #這裏不報錯
pay(p,200) #這裏報錯了
借用abc模塊來實現接口
from abc import ABCMeta,abstractmethod
class Payment(metaclass=ABCMeta):
@abstractmethod
def pay(self,money):
pass
class Wechatpay(Payment):
def fuqian(self,money):
print('微信支付了%s元'%money)
p = Wechatpay() #不調就報錯了
實踐中,繼承的第一種含義意義並不很大,甚至常常是有害的。因爲它使得子類與基類出現強耦合。
繼承的第二種含義非常重要。它又叫“接口繼承”。
接口繼承實質上是要求“做出一個良好的抽象,這個抽象規定了一個兼容接口,使得外部調用者無需關心具體細節,可一視同仁的處理實現了特定接口的所有對象”——這在程序設計上,叫做歸一化。
歸一化使得高層的外部使用者可以不加區分的處理所有接口兼容的對象集合——就好象linux的泛文件概念一樣,所有東西都可以當文件處理,不必關心它是內存、磁盤、網絡還是屏幕(當然,對底層設計者,當然也可以區分出“字符設備”和“塊設備”,然後做出針對性的設計:細緻到什麼程度,視需求而定)。
依賴倒置原則:
高層模塊不應該依賴低層模塊,二者都應該依賴其抽象;抽象不應該應該依賴細節;細節應該依賴抽象。換言之,要針對接口編程,而不是針對實現編程
在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
接口提取了一羣類共同的函數,可以把接口當做一個函數的集合。
然後讓子類去實現接口中的函數。
這麼做的意義在於歸一化,什麼叫歸一化,就是隻要是基於同一個接口實現的類,那麼所有的這些類產生的對象在使用時,從用法上來說都一樣。
歸一化,讓使用者無需關心對象的類是什麼,只需要的知道這些對象都具備某些功能就可以了,這極大地降低了使用者的使用難度。
比如:我們定義一個動物接口,接口裏定義了有跑、喫、呼吸等接口函數,這樣老鼠的類去實現了該接口,松鼠的類也去實現了該接口,由二者分別產生一隻老鼠和一隻松鼠送到你面前,即便是你分別不到底哪隻是什麼鼠你肯定知道他倆都會跑,都會喫,都能呼吸。
再比如:我們有一個汽車接口,裏面定義了汽車所有的功能,然後由本田汽車的類,奧迪汽車的類,大衆汽車的類,他們都實現了汽車接口,這樣就好辦了,大家只需要學會了怎麼開汽車,那麼無論是本田,還是奧迪,還是大衆我們都會開了,開的時候根本無需關心我開的是哪一類車,操作手法(函數調用)都一樣
5、抽象類
什麼是抽象類
與java一樣,python也有抽象類的概念但是同樣需要藉助模塊實現,抽象類是一個特殊的類,它的特殊之處在於只能被繼承,不能被實例化
爲什麼要有抽象類
如果說類是從一堆對象中抽取相同的內容而來的,那麼抽象類就是從一堆類中抽取相同的內容而來的,內容包括數據屬性和函數屬性。
比如我們有香蕉的類,有蘋果的類,有桃子的類,從這些類抽取相同的內容就是水果這個抽象的類,你喫水果時,要麼是喫一個具體的香蕉,要麼是喫一個具體的桃子。。。。。。你永遠無法喫到一個叫做水果的東西。
從設計角度去看,如果類是從現實對象抽象而來的,那麼抽象類就是基於類抽象而來的。
從實現角度來看,抽象類與普通類的不同之處在於:抽象類中有抽象方法,該類不能被實例化,只能被繼承,且子類必須實現抽象方法。這一點與接口有點類似,但其實是不同的,即將揭曉答案
在python中實現抽象類
抽象類與接口類
抽象類的本質還是類,指的是一組類的相似性,包括數據屬性(如all_type)和函數屬性(如read、write),而接口只強調函數屬性的相似性。
抽象類是一個介於類和接口直接的一個概念,同時具備類和接口的部分特性,可以用來實現歸一化設計
在python中,並沒有接口類這種東西,即便不通過專門的模塊定義接口,我們也應該有一些基本的概念。
多繼承問題
在繼承抽象類的過程中,我們應該儘量避免多繼承;
而在繼承接口的時候,我們反而鼓勵你來多繼承接口,接口隔離原則:
使用多個專門的接口,而不使用單一的總接口。即客戶端不應該依賴那些不需要的接口。
方法的實現
在抽象類中,我們可以對一些抽象方法做出基礎實現;
而在接口類中,任何方法都只是一種規範,具體的功能需要子類實現
ps:組合
面向對象的組合用法
軟件重用的重要方式除了繼承之外還有另外一種方式,即:組合
組合指的是,在一個類中以另外一個類的對象作爲數據屬性,稱爲類的組合
class Person:
def __init__(self,nickname,sex,hp,ad):
self.nickname = nickname
self.sex = sex
self.hp = hp
self.ad = ad
def attack(self,p1):
p1.hp -= self.ad
print('%s攻擊了%s,%s還剩%s血量'%(self.nickname,p1.nickname,p1.nickname,p1.hp))
def weapon_attack(self,wea):
# 武器類的對象封裝到人的對象中當做一個屬性.就叫做組合.
self.weapon = wea
class Weapon:
def __init__(self,name,att):
self.name = name
self.att = att
def Aux_attack(self,p,p1):
p1.hp -= self.att
print('%s利用%s打了%s%s滴血,%s還剩%s滴血' %(p.nickname,self.name,p1.nickname,self.att,p1.nickname,p1.hp))
# alex = Person('alex','男',100,20)
# barry = Person('太白','男',200,50)
# axe = Weapon('斧子',30)
# barry.weapon_attack(axe)
# barry.weapon.Aux_attack(barry,alex)
# axe.Aux_attack(alex)
# alex.attack(barry)
# alex.attack(barry)
圓環是由兩個圓組成的,圓環的面積是外面圓的面積減去內部圓的面積。圓環的周長是內部圓的周長加上外部圓的周長。
這個時候,我們就首先實現一個圓形類,計算一個圓的周長和麪積。然後在"環形類"中組合圓形的實例作爲自己的屬性來用
from math import pi
class Circle:
'''
定義了一個圓形類;
提供計算面積(area)和周長(perimeter)的方法
'''
def __init__(self,radius):
self.radius = radius
def area(self):
return pi * self.radius * self.radius
def perimeter(self):
return 2 * pi *self.radius
circle = Circle(10) #實例化一個圓
area1 = circle.area() #計算圓面積
per1 = circle.perimeter() #計算圓周長
print(area1,per1) #打印圓面積和周長
class Ring:
'''
定義了一個圓環類
提供圓環的面積和周長的方法
'''
def __init__(self,radius_outside,radius_inside):
self.outsid_circle = Circle(radius_outside)
self.inside_circle = Circle(radius_inside)
def area(self):
return self.outsid_circle.area() - self.inside_circle.area()
def perimeter(self):
return self.outsid_circle.perimeter() + self.inside_circle.perimeter()
ring = Ring(10,5) #實例化一個環形
print(ring.perimeter()) #計算環形的周長
print(ring.area()) #計算環形的面積
用組合的方式建立了類與組合的類之間的關係,它是一種‘有’的關係,比如教授有生日,教授教python課程
class BirthDate:
def __init__(self,year,month,day):
self.year=year
self.month=month
self.day=day
class Couse:
def __init__(self,name,price,period):
self.name=name
self.price=price
self.period=period
class Teacher:
def __init__(self,name,gender,birth,course):
self.name=name
self.gender=gender
self.birth=birth
self.course=course
def teach(self):
print('teaching')
p1=Teacher('egon','male',
BirthDate('1995','1','27'),
Couse('python','28000','4 months')
)
print(p1.birth.year,p1.birth.month,p1.birth.day)
print(p1.course.name,p1.course.price,p1.course.period)
'''
運行結果:
1 27
python 28000 4 months
'''
當類之間有顯著不同,並且較小的類是較大的類所需要的組件時,用組合比較好
多態
Pyhon不支持Java和C#這一類強類型語言中多態的寫法,但是原生多態,其Python崇尚“鴨子類型”。
class F1:
pass
class S1(F1):
def show(self):
print 'S1.show'
class S2(F1):
def show(self):
print 'S2.show'
# 由於在Java或C#中定義函數參數時,必須指定參數的類型
# 爲了讓Func函數既可以執行S1對象的show方法,又可以執行S2對象的show方法,所以,定義了一個S1和S2類的父類
# 而實際傳入的參數是:S1對象和S2對象
def Func(F1 obj):
"""Func函數需要接收一個F1類型或者F1子類的類型"""
print obj.show()
s1_obj = S1()
Func(s1_obj) # 在Func函數中傳入S1類的對象 s1_obj,執行 S1 的show方法,結果:S1.show
s2_obj = S2()
Func(s2_obj) # 在Func函數中傳入Ss類的對象 ss_obj,執行 Ss 的show方法,結果:S2.show
Python僞代碼實現Java或C#的多態
class F1:
pass
class S1(F1):
def show(self):
print 'S1.show'
class S2(F1):
def show(self):
print 'S2.show'
def Func(obj):
print obj.show()
s1_obj = S1()
Func(s1_obj)
s2_obj = S2()
Func(s2_obj)
Python “鴨子類型”
封裝
隱藏對象的屬性和實現細節,僅對外提供公共訪問方式。
【好處】
-
將變化隔離;
-
便於使用;
-
提高複用性;
-
提高安全性;
【封裝原則】
1. 將不需要對外提供的內容都隱藏起來;
2. 把屬性都隱藏,提供公共方法對其訪問。
私有變量和私有方法(在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是可以訪問到的,即這種操作並不是嚴格意義上的限制外部訪問,僅僅只是一種語法意義上的變形
這種自動變形的特點:
1.類中定義的__x只能在內部使用,如self.__x,引用的就是變形的結果。
2.這種變形其實正是針對外部的變形,在外部是無法通過__x這個名字訪問到的。
3.在子類定義的__x不會覆蓋在父類定義的__x,因爲子類中變形成了:_子類名__x,而父類中變形成了:_父類名__x,即雙下滑線開頭的屬性在繼承給子類時,子類是無法覆蓋的。
這種變形需要注意的問題是:
1.這種機制也並沒有真正意義上限制我們從外部直接訪問屬性,知道了類名和屬性名就可以拼出名字:_類名__屬性,然後就可以訪問了,如a._A__N
2.變形的過程只在類的內部生效,在定義後的賦值操作,不會變形
私有方法(在繼承中,父類如果不想讓子類覆蓋自己的方法,可以將方法定義爲私有的)
#正常情況
>>> 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
複製代碼
封裝與擴展性
封裝在於明確區分內外,使得類實現者可以修改封裝內的東西而不影響外部調用者的代碼;而外部使用用者只知道一個接口(函數),只要接口(函數)名、參數不變,使用者的代碼永遠無需改變。這就提供一個良好的合作基礎——或者說,只要接口這個基礎約定不變,則代碼改變不足爲慮。
#類的設計者
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
#類的設計者,輕鬆的擴展了功能,而類的使用者完全不需要改變自己的代碼
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()