一 引子
從封裝本身的意思去理解,封裝就好像是拿來一個麻袋,把青菜,土豆,花菜,還有蘋果一起裝進(jìn)麻袋,然后把麻袋封上口子。照這種邏輯看,封裝=‘隱藏’,這種理解是相當(dāng)片面的。
在面向?qū)ο笾羞@個麻袋就是你的類或者對象,類或者對象這倆麻袋內(nèi)部裝了數(shù)據(jù)屬性和函數(shù)屬性,那么對于類和對象來說"封"的概念從何而來,其實(shí)封的概念代表隱藏。
在學(xué)完了面向?qū)ο蟮念惡蛯ο笙嚓P(guān)的知識后,大家都知道了如何把屬性裝進(jìn)類或者對象中,那么如何完成封的效果呢?
第一個層面的封裝:類就是麻袋,這本身就是一種封裝
?
第二個層面的封裝:類中定義私有的,只在類的內(nèi)部使用,外部無法訪問
?
python不依賴語言特性去封裝數(shù)據(jù),而是通過遵循一定的數(shù)據(jù)屬性和函數(shù)屬性的命名約定來達(dá)到封的效果
約定一:任何一單下劃線開頭的名字都應(yīng)該是內(nèi)部的,私有的
#
_*_coding:utf-8_*_
__author__
=
'
Simon
'
class
People:
__star
=
'
earth111111111111
'
#
單下劃線開頭的就代表隱藏起來的
__star1
=
'
earth111111111111
'
__star2
=
'
earth111111111111
'
__star3
=
'
earth111111111111
'
def
__init__
(self,id,name,age,salary):
print
(
'
----->
'
,self.
__star
)
self.id
=
id
self.name
=
name
self.age
=
age
self.salary
=
salary
def
get_id(self):
print
(
'
我是私有方法啊,我找到的id是[%s]
'
%
self.id)
#
訪問函數(shù)
def
get_star(self):
print
(self.
__star
)
p1
=People(
'
123123123123
'
,
'
alex
'
,
'
18
'
,100000000
)
#
print(p1.__star)
print
(People.
__dict__
)
#
print(p1.__star)
print
(p1._People__star)
#
#
p1.get_star()
p1.get_star()
python 并不會真的阻止你訪問私有的屬性,模塊也遵循這種約定,如果模塊名以單下劃線開頭,那么from module import *時不能被導(dǎo)入,但是你from module import _private_module 依然是可以導(dǎo)入的
其實(shí)很多時候你去調(diào)用一個模塊的功能時會遇到單下劃線開頭的
(socket._socket,sys._home,sys._clear_type_cache),這些都是私有的,原則上是供內(nèi)部調(diào)用的,作為外部的你,一意孤行也是可以用的,只不過顯得稍微傻一點(diǎn)。
約定二:雙下劃線開頭的名字
?
第三個層面的封裝:明確區(qū)分內(nèi)外,內(nèi)部的實(shí)現(xiàn)邏輯,外部無法知曉,并且為封裝到內(nèi)部的邏輯提供一個訪問接口給外部使用(這才是真正的封裝,具體實(shí)現(xiàn),會在面向?qū)ο筮M(jìn)階中講)
?
二 先看如何隱藏
在python中用雙下劃線開頭的方式將屬性隱藏起來(設(shè)置成私有的)
#其實(shí)這僅僅這是一種變形操作且僅僅只在類定義階段發(fā)生變形
#類中所有雙下劃線開頭的名稱如__x都會在類定義時自動變形成:_類名__x的形式:
class A:
__N=0 #類的數(shù)據(jù)屬性就應(yīng)該是共享的,但是語法上是可以把類的數(shù)據(jù)屬性設(shè)置成私有的如__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() #只有在類內(nèi)部才可以通過__foo的形式訪問到.
#A._A__N是可以訪問到的,
#這種
,在外部是無法通過__x這個名字訪問到。
?
這種變形需要注意的問題是:
1.這種機(jī)制也并沒有真正意義上限制我們從外部直接訪問屬性,知道了類名和屬性名就可以拼出名字:_類名__屬性,然后就可以訪問了,如a._A__N,
即這種操作并不是嚴(yán)格意義上的限制外部訪問,僅僅只是一種語法意義上的變形,主要用來限制外部的直接訪問。
2.變形的過程只在類的定義時發(fā)生一次,在定義后的賦值操作,不會變形
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
()
#
只會與自己所在的類為準(zhǔn),即調(diào)用_A__fa
...
>>>
class
B(A):
...
def
__fa
(self):
...
print
(
'
from B
'
)
...
>>> b=
B()
>>>
b.test()
from
A
三? 封裝不是單純意義的隱藏
封裝的真諦在于明確地區(qū)分內(nèi)外,封裝的屬性可以直接在內(nèi)部使用,而不能被外部直接使用,然而定義屬性的目的終歸是要用,外部要想用類隱藏的屬性,需要我們?yōu)槠溟_辟接口,讓外部能夠間接地用到我們隱藏起來的屬性,那這么做的意義何在???
1:封裝數(shù)據(jù):將數(shù)據(jù)隱藏起來這不是目的。隱藏起來然后對外提供操作該數(shù)據(jù)的接口,然后我們可以在接口附加上對該數(shù)據(jù)操作的限制,以此完成對數(shù)據(jù)屬性操作的嚴(yán)格控制。
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:封裝方法:目的是隔離復(fù)雜度
封裝方法舉例:?
1. 你的身體沒有一處不體現(xiàn)著封裝的概念:你的身體把膀胱尿道等等這些尿的功能隱藏了起來,然后為你提供一個尿的接口就可以了(接口就是你的。。。,),你總不能把膀胱掛在身體外面,上廁所的時候就跟別人炫耀:hi,man,你瞅我的膀胱,看看我是怎么尿的。
2. 電視機(jī)本身是一個黑盒子,隱藏了所有細(xì)節(jié),但是一定會對外提供了一堆按鈕,這些按鈕也正是接口的概念,所以說,封裝并不是單純意義的隱藏!!!
3.?快門就是傻瓜相機(jī)為傻瓜們提供的方法,該方法將內(nèi)部復(fù)雜的照相功能都隱藏起來了
提示:在編程語言里,對外提供的接口(接口可理解為了一個入口),可以是函數(shù),稱為接口函數(shù),這與接口的概念還不一樣,接口代表一組接口函數(shù)的集合體。
#
取款是功能,而這個功能有很多功能組成:插卡、密碼認(rèn)證、輸入金額、打印賬單、取錢
#
對使用者來說,只需要知道取款這個功能即可,其余功能我們都可以隱藏起來,很明顯這么做
#
隔離了復(fù)雜度,同時也提升了安全性
class
ATM:
def
__card
(self):
print
(
'
插卡
'
)
def
__auth
(self):
print
(
'
用戶認(rèn)證
'
)
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()
隔離復(fù)雜度的例子
3: 了解
python并不會真的阻止你訪問私有的屬性,模塊也遵循這種約定,如果模塊名以單下劃線開頭,那么from module import *時不能被導(dǎo)入,但是你from module import _private_module依然是可以導(dǎo)入的
其實(shí)很多時候你去調(diào)用一個模塊的功能時會遇到單下劃線開頭的(socket._socket,sys._home,sys._clear_type_cache),這些都是私有的,原則上是供內(nèi)部調(diào)用的,作為外部的你,一意孤行也是可以用的,只不過顯得稍微傻逼一點(diǎn)點(diǎn)
python要想與其他編程語言一樣,嚴(yán)格控制屬性的訪問權(quán)限,只能借助內(nèi)置方法如__getattr__,詳見面向?qū)ο筮M(jìn)階
四 特性(property)?
什么是特性property
property是一種特殊的屬性,訪問它時會執(zhí)行一段功能(函數(shù))然后返回值
例一:BMI指數(shù)(bmi是計算而來的,但很明顯它聽起來像是一個屬性而非方法,如果我們將其做成一個屬性,更便于理解)
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)
?
例二:圓的周長和面積?
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)
#
可以向訪問數(shù)據(jù)屬性一樣去訪問area,會觸發(fā)一個函數(shù)的執(zhí)行,動態(tài)計算出一個值
print
(c.perimeter)
#
同上
'''
輸出結(jié)果:
314.1592653589793
62.83185307179586
'''
#注意:此時的特性arear和perimeter不能被賦值
c.area=3 #為特性area賦值
'''
拋出異常:
AttributeError: can't set attribute
'''
為什么要用property
將一個類的函數(shù)定義成特性以后,對象再去使用的時候obj.name,根本無法察覺自己的name是執(zhí)行了一個函數(shù)然后計算出來的,這種特性的使用方式 遵循了統(tǒng)一訪問的原則
除此之外,看下
ps:面向?qū)ο蟮姆庋b有三種方式:
【public】
這種其實(shí)就是不封裝,是對外公開的
【protected】
這種封裝方式對外不公開,但對朋友(friend)或者子類(形象的說法是“兒子”,但我不知道為什么大家 不說“女兒”,就像“parent”本來是“父母”的意思,但中文都是叫“父類”)公開
【private】
這種封裝對誰都不公開
python并沒有在語法上把它們?nèi)齻€內(nèi)建到自己的class機(jī)制中,在C++里一般會將所有的所有的數(shù)據(jù)都設(shè)置為私有的,然后提供set和get方法(接口)去設(shè)置和獲取,在python中通過property方法可以實(shí)現(xiàn)
class Foo:
def __init__(self,val):
self.__NAME=val #將所有的數(shù)據(jù)屬性都隱藏起來
@property
def name(self):
return self.__NAME #obj.name訪問的是self.__NAME(這也是真實(shí)值的存放位置)
@name.setter
def name(self,value):
if not isinstance(value,str): #在設(shè)定值之前進(jìn)行類型檢查
raise TypeError('%s must be str' %value)
self.__NAME=value #通過類型檢查后,將值value存放到真實(shí)的位置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
Foo:
def
__init__
(self,val):
self.
__NAME
=val
#
將所有的數(shù)據(jù)屬性都隱藏起來
def
getname(self):
return
self.
__NAME
#
obj.name訪問的是self.__NAME(這也是真實(shí)值的存放位置)
def
setname(self,value):
if
not
isinstance(value,str):
#
在設(shè)定值之前進(jìn)行類型檢查
raise
TypeError(
'
%s must be str
'
%
value)
self.
__NAME
=value
#
通過類型檢查后,將值value存放到真實(shí)的位置self.__NAME
def
delname(self):
raise
TypeError(
'
Can not delete
'
)
name
=property(getname,setname,delname)
#
不如裝飾器的方式清晰
了解:一種property的古老用法
五 封裝與擴(kuò)展性
封裝在于明確區(qū)分內(nèi)外,使得類實(shí)現(xiàn)者可以修改封裝內(nèi)的東西而不影響外部調(diào)用者的代碼;而外部使用用者只知道一個接口(函數(shù)),只要接口(函數(shù))名、參數(shù)不變,使用者的代碼永遠(yuǎn)無需改變。這就提供一個良好的合作基礎(chǔ)——或者說,只要接口這個基礎(chǔ)約定不變,則代碼改變不足為慮。
?
#
類的設(shè)計者
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):
#
對外提供的接口,隱藏了內(nèi)部的實(shí)現(xiàn)細(xì)節(jié),此時我們想求的是面積
return
self.
__width
* self.
__length
#
使用者
>>> r1=Room(
'
臥室
'
,
'
egon
'
,20,20,20
)
>>> r1.tell_area()
#
使用者調(diào)用接口tell_area
#
類的設(shè)計者,輕松的擴(kuò)展了功能,而類的使用者完全不需要改變自己的代碼
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):
#
對外提供的接口,隱藏內(nèi)部實(shí)現(xiàn),此時我們想求的是體積,內(nèi)部邏輯變了,只需求修該下列一行就可以很簡答的實(shí)現(xiàn),而且外部調(diào)用感知不到,仍然使用該方法,但是功能已經(jīng)變了
return
self.
__width
* self.
__length
* self.
__high
#
對于仍然在使用tell_area接口的人來說,根本無需改動自己的代碼,就可以用上新功能
>>> r1.tell_area()
?
六 面向?qū)ο蟮膬?yōu)點(diǎn)
從編程進(jìn)化論我們得知,面向?qū)ο笫且环N更高等級的結(jié)構(gòu)化編程方式,他的好處有兩點(diǎn):
1、通過封裝明確了內(nèi)外,你作為類的締造者,你是上帝,上帝造物的邏輯你無需知道,上帝想讓你知道的你才能知道,這樣就明確了劃分了等級,物就是調(diào)用者,上帝就是物的創(chuàng)造者
2、通過繼承+多態(tài)在語言層面支持了歸一化設(shè)計
注意:不用面向?qū)ο笳Z言(即不用class),一樣可以做歸一化(如老掉牙的泛文件概念、游戲行業(yè)的一切皆精靈),一樣可以封裝(通過定義模塊和接口),只是用面向?qū)ο笳Z言可以直接用語言元素顯示聲明這些而已;而用了面向?qū)ο笳Z言,滿篇都是class,并不等于就有了歸一化的設(shè)計。甚至,因?yàn)楸贿@些花哨的東西迷惑,反而更加不知道什么才是設(shè)計。
?
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

