1. 程式人生 > >Python—面向對象 封裝03

Python—面向對象 封裝03

bject 私有化 父類 setter 特殊 提升 ict 賬單 run

接著上面的一篇繼續往下:

如何隱藏

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

class A:
    __x = 1  # _A__x = 1

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

    def __fun1(self):
        print("run fun1.......")

    def fun2(self):
        self.__fun1()  # self._A__fun1()
        print("run fun2.......")

a = A("Astro")
# print(a.x)  # 報錯 無法訪問到 x   AttributeError: 'A' object has no attribute 'x'
print(A.__dict__)
# {'__module__': '__main__', '_A__x': 1, ........}
# _A__x
print(a._A__x)  # 1

a.fun2()
# run fun1.......
# run fun2.......

?

其實這僅僅這是一種變形操作
類中所有雙下劃線開頭的名稱如__x都會自動變形成:_類名__x的形式:
A._A__N是可以訪問到的,即這種操作並不是嚴格意義上的限制外部訪問,僅僅只是一種語法意義上的變形

這種變形的特點:

  • 在類外部無法直接obj.__AttrName
  • 在類內部是可以直接使用:obj.__AttrName
  • 在子類定義的__x不會覆蓋在父類定義的__x,因為子類中變形成了:_子類名__x,而父類中變形成了:_父類名__x,即雙下滑線開頭的屬性在繼承給子類時,子類是無法覆蓋的。

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

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

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

a.__g = "gd"
print(a.__dict__)
# {'_A__name': 'Astro', '__g': 'gd'}

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

# 正常情況
class A:
    def foo(self):
        print('A.foo')

    def bar(self):
        print('A.bar')
        self.foo() #b.foo()

class B(A):
    def foo(self):
        print('B.foo')

b=B()
b.bar()
# A.bar
# B.foo

?

?

# 私有化後
class A:
    def __foo(self): # #在定義時就變形為 _A__foo
        print('A.foo')

    def bar(self):
        print('A.bar')
        self.__foo() #self._A__foo()   只會與自己所在的類為準,即調用_A__fa

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

b=B()
b.bar()
# A.bar
# A.foo

?

?


?

封裝的意義

1.封裝數據

# 封裝數據屬性:明確的區分內外
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):  # 判斷 傳進來的 name 是不是 str 類型
            print("name must be str type")
            return
        if not isinstance(age, int):
            print("age must be int type")
            return
        self.__name = name
        self.__age = age



p = People("Astro", 15)
p.tell_info()  # name:Astro  age:15
# 通過調用 tell_info 這個接口,來間接訪問 name 和 age

p.set_info("茶水博士", 60)
p.tell_info()  # name:茶水博士  age:60

p.set_info(1111,222)  # name must be str type

?

?

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()
# 插卡
# 用戶認證
# 輸入取款金額
# 打印賬單
# 取款

#----------------------------
#取款是功能,而這個功能有很多功能組成:插卡、密碼認證、輸入金額、打印賬單、取錢
#對使用者來說,只需要知道取款這個功能即可,其余功能我們都可以隱藏起來,很明顯這麽做
#隔離了復雜度,同時也提升了安全性
  • 電視機本身是一個黑盒子,隱藏了所有細節,但是一定會對外提供了一堆按鈕,這些按鈕也正是接口的概念,所以說,封裝並不是單純意義的隱藏!!!
  • 快門就是傻瓜相機為傻瓜們提供的方法,該方法將內部復雜的照相功能都隱藏起來了

?

?


?

3.特性(Property)

什麽是特性 property

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)

p = People("astro", 55, 1.73)
# print(p.bmi())  # 18.376825152861773
# 我們想得到 bmi 這個值,需要調用 p.bmi() ,如果在 def bmi(self) 上面加 @property ,即可直接調用了

print(p.bmi)  # 18.376825152861773

p.height = 1.8
print(p.bmi)  # 16.975308641975307

p.bmi = 20  # 報錯 AttributeError: can't set attribute

為什麽要用property

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


?

ps:面向對象的封裝有三種方式:
【public】
這種其實就是不封裝,是對外公開的
【protected】
這種封裝方式對外不公開,但對朋友(friend)或者子類公開
【private】
這種封裝對誰都不公開

?

python並沒有在語法上把它們三個內建到自己的class機制中,在C++裏一般會將所有的所有的數據都設置為私有的,然後提供set和get方法(接口)去設置和獲取,在python中通過property方法可以實現

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

    @property
    def name(self):
        return self.__name  # obj.name訪問的是self.__NAME(這也是真實值的存放位置)

    @name.setter
    def name(self, val):
        if not isinstance(val, str):  # 在設定值之前進行類型檢查
            print("姓名必須為字符串")
            return
        self.__name = val
    @name.deleter
    def name(self):
        print("deleter....")
        print("不允許刪除....")

p = People('astro')
print(p.name)  # astro  此時可以直接訪問 name 屬性了

p.name = 'Astro'
print(p.name)  # Astro name 別改變了,執行了 @name.setter 下的方法

p.name = 111  # 姓名必須為字符串

del p.name  # deleter....   不允許刪除....

Python—面向對象 封裝03