1. 程式人生 > >面向對象三大特性——封裝(含property)

面向對象三大特性——封裝(含property)

普通 金額 訪問 控制 instance @property 進行 安全 AI

  封裝是面向對象的特征之一,是對象和類概念的主要特性。

封裝概念

  封裝就是把客觀事物封裝成抽象的類,並且類可以把自己的數據和方法只讓可信的類或者對象操作,對不可信的進行信息隱藏。

隱藏屬性

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

  其實這僅僅這是一種變形操作,類中所有雙下劃線開頭的名稱如__x都會自動變形成:_類名__x的形式。

class A:
    __x = 1  # _A__x = 1

    def __init__(self, name):
        self.__name = name  # self._A__name=‘egon‘
def __foo(self): # _A__foo print(%s foo run % self.__name) def bar(self): self.__foo() # self._A__foo() print(from bar) # 無法找到類的屬性和函數: # print(A.__x) # print(A.__foo) print(A.__dict__) # 可以查看到_A__foo;bar這兩個函數 # 輸出:{‘__module__‘: ‘__main__‘, ‘_A__x‘: 1, ‘__init__‘: <function A.__init__ at 0x101f211e0>, ‘_A__foo‘: <function A.__foo at 0x101f21378>, ‘bar‘: <function A.bar at 0x101f212f0>, ‘__dict__‘: <attribute ‘__dict__‘ of ‘A‘ objects>, ‘__weakref__‘: <attribute ‘__weakref__‘ of ‘A‘ objects>, ‘__doc__‘: None}
a = A(egon) a._A__foo() # 通過這種方式可以訪問類隱藏函數 # 輸出:egon foo run a.bar() """ egon foo run from bar """

  可以看到類的屬性和函數在前面加‘__‘,在類定義階段就發生了變形,變形後在外部就無法通過.__x或.__func來調用。

這種自動變形的特點

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

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

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

class Foo:
    def __func(self):   # _Foo_func
        print(from foo)


class Bar(Foo):
    def __func(self):   # _Bar__func
        print(from bar)
        
b = Bar()
# b.func()   # AttributeError:沒有這個屬性
b._Bar__func()  # 輸出:from bar

變形需要註意的問題

  1、知道了類名和屬性名就可以拼出名字:_類名__屬性,然後就可以訪問了

class B:
    __x = 1

    def __init__(self, name):
        self.__name = name

print(B._B__x)
"""
1
"""

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

>>> class A:
...     def __init__(self):
...             self.__X=10
... 
>>> a=A()
>>> a.__dict__
{_A__X: 10}
>>> a.__Y=2131
>>> a.__dict__
{_A__X: 10, __Y: 2131}    # __Y沒有變形

  3、在繼承中,父類如果不想讓子類覆蓋自己的方法,可以將方法定義為私有的

class A:
    def __foo(self):  # _A__foo
        print(A foo)

    def bar(self):
        print(A.bar)
        self.__foo()  # self._A__foo()

class B(A):
    def __foo(self):  # _B__foo
        print(B.foo)

b = B()
b.bar()
"""
A.bar
A foo  # 只在自己類找方法不去其他類查找,子類不覆蓋父類方法
"""
技術分享圖片
#正常情況
>>> 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 People:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age

    def tell_info(self):
        print(Name:<%s> Age:<%s> % (self.__name, self.__age))

    def set_info(self, name, age):
        if not isinstance(name, str):
            print(名字必須是字符串類型)
            return
        if not isinstance(age, int):
            print(年齡必須是數字類型)
            return
        self.__name = name
        self.__age = age


p = People(egon, 18)

# p.tell_info()
"""
Name:<egon> Age:<18>  # 封裝數據,開放接口給外部訪問
"""

# p.set_info(‘Egon‘, 38) # 修改數據只能通過接口來完成,可以通過接口完成各種限制
# p.tell_info()
"""
Name:<Egon> Age:<38>
"""

# p.set_info(123, 38)
"""
名字必須是字符串類型
"""
p.set_info(egon, 38)
p.tell_info()
"""
年齡必須是數字類型
Name:<egon> Age:<18>
"""
封裝數據屬性

二、封裝方法

  隔離復雜度。

技術分享圖片
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()
"""
插卡
輸入取款金額
輸入取款金額
打印賬單
取款
"""
封裝方法

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

封裝和擴展性

  封裝在於明確區分內外,使得類實現者可以修改封裝內的東西而不影響外部調用者的代碼;而外部使用用者只知道一個接口(函數),只要接口(函數)名、參數不變,使用者的代碼永遠無需改變

class Room:
    def __init__(self, name, owner, weight, length, height):
        self.name = name
        self.owner = owner

        self.__weight = weight
        self.__length = length
        self.__height = height

    def tell_area(self):
        return self.__weight * self.__length


r = Room(衛生間, alex, 10, 10, 10)

print(r.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


#使用者
>>> 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()
封裝與擴展

特性(property)

property概念

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

  下面以計算BMI指數為例:(bmi是計算而來的,但很明顯它聽起來像是一個屬性而非方法,如果我們將其做成一個屬性,更便於理解)

  BMI指數:(BMI是計算而來的,很明顯它聽起來像一個屬性而非方法,如果我們將其作為一個屬性,更便於理解)
  成人的BMI數值:
  過輕:低於18.5
  正常:18.5-23.9
  過重:24-27
  肥胖:28-32
  非常肥胖:高於32
  體質指數(BMI)= 體重(KG)/ 身高^2(M)
  EX:70KG / (1.75*1.75) = 22.86

方法一:普通解決辦法

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

p = People(jack, 48, 1.65)
p.bmi = p.weight / (p.height ** 2)

print(p.bmi)
"""
17.63085399449036
egon
dragon
名字必須是字符串類型
dragon
不允許刪除
"""

方法二:添加函數改寫

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

    def bmi(self):
        print(===>)
        return self.weight / (self.height ** 2)

p = People(SH, 53, 1.70)
print(p.bmi())  # bmi是一個名詞,卻使用bmi(),容易誤解為一個動作

方法三:添加property裝飾器

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

    @property    # 應用場景:有一個值是通過計算得來的,首選定義方法,運用property讓使用者感知不到
    def bmi(self):
        print(===>)
        return self.weight / (self.height ** 2)  # 必須有返回值

p = People(SH, 53, 1.70)
print(p.bmi)   # 使用者像訪問數據屬性一樣訪問bmi,方法被偽裝
"""
===>
18.339100346020764
"""
p.height = 1.82
print(p.bmi)
"""
===>
16.000483033450067
"""
p.bmi = 333  # 報錯,看起來像數據屬性,其實還是一個方法,不能賦值

property好處

  將一個類的函數定義成特性以後,對象再去使用的時候obj.name,根本無法察覺自己的name是執行了一個函數然後計算出來的,這種特性的使用方式遵循了統一訪問的原則。總結來說:把一些計算得到的屬性偽裝得像數據屬性一樣被用戶訪問。

property其他用法(set\get\delete方法)

  在C++裏一般會將所有的所有的數據都設置為私有的,然後提供set和get方法(接口)。Python中通過property來實現這個功能:

class People:
    def __init__(self, name):
        self.__name = name

    def get_name(self):
        return self.__name

p = People(egon)
print(p.get_name())  # 輸出:egon

  在上面的代碼中,People的name屬性被封裝,不能直接訪問,因此給類添加了一個get_name()方法來查看內部已經封裝的屬性。

  但是這種這種情況下,用戶的屬性調用方式發生了改變。可以通過@property解決該問題。

class People:
    def __init__(self, name):
        self.__name = name

    @property
    def name(self):
        return self.__name

p = People(egon)
print(p.name)   # 輸出:egon

  調用方式已經和普通屬性相同,但是在上面bmi的代碼中可以看到,這種情況下是不能對隱藏屬性賦值的。要實現賦值,需要運用@name.setter裝飾器(name被property裝飾後才可用)進行如下修改:

class People:
    def __init__(self, name):
        self.__name = name

    @property
    def name(self):
        # print(‘getter‘)
        return self.__name

    @name.setter
    def name(self, val):
        # print(‘setter‘, val)
        if not isinstance(val, str):
            print(名字必須是字符串類型)
            return
        self.__name=val

    @name.deleter
    def name(self):
        # print(‘deleter‘)
        print(不允許刪除)

p = People(egon)
print(p.name)    # 輸出:egon
p.name = dragon
print(p.name)   # 輸出:dragon  # name修改成功

p.name = 123   # 輸出:名字必須是字符串類型(報錯)
print(p.name)  # 輸出:dragon

del p.name  # 輸出:不允許刪除(報錯)

  如代碼所示,實現了name屬性的修改和刪除,@name.deleter和@name.setter都是基於name被@property裝飾才可用的

面向對象三大特性——封裝(含property)