Python之封裝


1、為什么要封裝

封裝不是單純意義的隱藏

1:封裝數據:主要原因是:保護私隱,明確區分內外。將數據隱藏起來這不是目的。隱藏起來然后對外提供操作該數據的接口,然后我們可以在接口附加上對該數據操作的限制,以此完成對數據屬性操作的嚴格控制。

class Teacher:
    def __init__(self,name,age):
        self.__name=name
        self.__age=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()
View Code

 

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

封裝方法舉例:

1. 你的身體沒有一處不體現着封裝的概念:你的身體把膀胱尿道等等這些尿的功能隱藏了起來,然后為你提供一個尿的接口就可以了(接口就是你的。。。,),你總不能把膀胱掛在身體外面,上廁所的時候就跟別人炫耀:hi,man,你瞅我的膀胱,看看我是怎么尿的。

2. 電視機本身是一個黑盒子,隱藏了所有細節,但是一定會對外提供了一堆按鈕,這些按鈕也正是接口的概念,所以說,封裝並不是單純意義的隱藏!!!

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

提示:在編程語言里,對外提供的接口(接口可理解為了一個入口),可以是函數,稱為接口函數,這與接口的概念還不一樣,接口代表一組接口函數的集合體。

#取款是功能,而這個功能有很多功能組成:插卡、密碼認證、輸入金額、打印賬單、取錢
#對使用者來說,只需要知道取款這個功能即可,其余功能我們都可以隱藏起來,很明顯這么做隔離了復雜度,同時也提升了安全性

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()
View Code

 

2、封裝例子

__名字,這種語法,只在定義的時候才會有變形的效果,如果類或者對象已經產生了,就不會有變形效果

classA:
    __x=1 #在屬性前面加兩個下划線,表示對該屬性進行隱藏,設置成私有,在內部都會變成成:_類名.__x
    def__test(self): #這里在內部會變形:_A__test,調用的時候a._A__test()
        print('fromA')
    def__init__(self):
        self.__x=10 #變形為self._A__x

    def__foo(self): #變形為_A.__foo
        print('fromA')

    def bar(self):
        self.__foo()  #只有在類內部才可以通過__foo的形式訪問

#這就是封裝,簡單的隱藏
#a=A()
#print(a._A__x)
#a._A__test()#不建議在外部直接通過這種方式調用隱藏方法
例1

這種自動變形的特點:

1.類中定義的__x只能在內部使用,如self.__x,引用的就是變形的結果。

2.這種變形其實正是針對外部的變形,在外部是無法通過__x這個名字訪問到的。

3.在子類定義的__x不會覆蓋在父類定義的__x,因為子類中變形成了:_子類名__x,而父類中變形成了:_父類名__x,即雙下滑線開頭的屬性在繼承給子類時,子類是無法覆蓋的。

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

1.這種機制也並沒有真正意義上限制我們從外部直接訪問屬性,知道了類名和屬性名就可以拼出名字:_類名__屬性,然后就可以訪問了,如a._A__N

2.變形的過程只在類的定義是發生一次,在定義后的賦值操作,不會變形

class A:
    def fa(self):
        print('from A_fa')

    def test(self):
        self.fa()  # b.fa

class B(A):
    def fa(self):
        print('from B_fa')
    pass

b=B()
b.test() # b.test --- >B---->A----> b.fa()----> b 是 B 的對象,在 B 里找 fa
例2:問題 b.test() 到底找到誰
class A:
    def __init__(self):
        self.__x=1  # 對象調用的時候 self._A__x 或者定義一個接口

    def tell(self):   # 定義統一接口,對象去調用這個 tell() 接口查看 __x
        print(self.__x)  # 在類的內部,可以直接這樣調用 __x,外部是 _A__x

# 法1:不推薦
a=A()
print(a._A__x)

# 法2: 可以用
a.tell()
例3
# 知識點:在繼承中,父類如果不想讓子類覆蓋自己的方法,可以將方法定義為私有的
# __名字,在定義節點就已經變形了,變成  _類名__屬性
class A:
    def __fa(self):  # 變形:_A__fa,fa 變成私有
        print('from A_fa')

    def test(self):
        self.__fa()  # 在定義的時候就變形了,self.__fa() 變成:self._A__fa()

class B(A):
    def __fa(self):  # 變形:_B__fa
        print('from B_fa')
    pass

b=B()
b.test() # b.test --- >B沒有---->找A的test----> b.fa()----> b._A__fa  找的A的 __fa
例4:問題: b.test() 找的誰

 

3、封裝特性

什么是特性property

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

為什么要用property

將一個類的函數定義成特性以后,對象再去使用的時候obj.name,根本無法察覺自己的name是執行了一個函數然后計算出來的,這種特性的使用方式遵循了統一訪問的原則

import math
class Circle:  # 圓周率
    def __init__(self,radius):  # 圓的半徑
        self.radius=radius

    @property   # area=property(area)
    def area(self):
        return math.pi * self.radius**2  #  計算圓的面積

    @property
    def perimeter(self):
        return 2*math.pi*self.radius # 計算周長

c=Circle(7)
print(c.area)  # 偽裝成數據屬性,如果不加 property 的話,調用的時候變成  c.area(),一個函數屬性

property 簡單來說就把類里的函數屬性偽裝成一個數據屬性,使用者用起來感覺不到自己用的其實一個函數



#注意:此時的特性arear和perimeter不能被賦值,因為實質上是一個函數屬性
c.area=3 #為特性area賦值
'''
拋出異常:
AttributeError: can't set attribute
'''
圓周率例子

 

ps:面向對象的封裝有三種方式:

【public】

這種其實就是不封裝,是對外公開的

【protected】

這種封裝方式對外不公開,但對朋友(friend)或者子類(形象的說法是“兒子”,但我不知道為什么大家不說“女兒”,就像“parent”本來是“父母”的意思,但中文都是叫“父類”)公開

【private】

這種封裝對誰都不公開

 

class people:
    def __init__(self,name,age,height,weight):
        self.name=name
        self.age=age
        self.height=height
        self.weight=weight


    @property
    def bodyindex(self):
        return self.weight/(self.height**2)

p1=people('alex',18,1.76,74)
print(p1.bodyindex)
例1
class people:
    def __init__(self,name,SEX):
        self.name=name
        # self.__sex=SEX  # 性別隱藏起來不讓人知道
        self.sex=SEX  # # p2.sex=male 一初始化或賦值操作就找 sex.setter
    @property   # 負責查詢
    def sex(self):   # 通過接口可以查看隱藏的性別
        return self.__sex  # p2.__sex= male

    @sex.setter  # 定義修改性別的接口
    def sex(self,value):
        # sexes=['male','female']
        if not isinstance(value,str):  # 在設定值之前進行類型檢查,增加限制的擴展性
            raise TypeError('性別必須是字符串類型')
        # if value not in sexes:
        #     raise TypeError('性別只能是 male 或者 female')
        self.__sex=value  # p2.__sex= male

    @sex.deleter
    def sex(self):   # 刪除屬性接口
        del self.__sex  # del p2.__sex

p2=people('alex','male')  # 觸發 init 執行,這里有個賦值操作 p2.sex='male'
print(p2.sex)
# p2.sex='female3'
# print(p2.sex)
del p2.sex  # 刪掉 __sex 數據屬性
print(p2.sex)  # 再去 property 找的話找不到了
例2

被 property 裝飾的屬性會優先於對象的屬性被使用,被找到

而被 property裝飾的屬性,如 sex ,分成三種

property 查詢

sex.setter 賦值,修改

sex.deleter 刪除

如果對象要修改數據屬性的時候,在沒有 property 的情況下,可以隨便改,但是加了之后,就可以有一個擴展性,限制對象只能改什么


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM