『無為則無心』Python面向對象 — 51、私有成員變量(類中數據的封裝)

1、私有成員變量介紹

(1)私有成員變量概念

在Python面向對象中,把類的某些屬性,如果在使用的過程中,不希望被外界直接訪問,就可以將該屬性設置為私有的,即只有當前類持有,然後暴露給外界一個訪問的函數,來實現間接的訪問對象屬性,這就是類中數據的封裝。

如果類中的屬性不想被外界直接訪問,則可以在屬性的前面添加兩個下劃線__,此時稱該屬性為私有屬性,即私有成員變量。

封裝的本質:就是屬性的私有化。

(2)私有成員變量特點

只能在類的內部被直接訪問,在外界不能直接訪問。

(3)私有成員變量體驗

1)屬性不被私有化情況:

# 1.屬性不私有化的時候
class Student():
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def tellMe(self):
        print(f"大家好,我是{self.name},年齡{self.age}")


# 創建對象
stu = Student("孫悟空", 18)

# 通過對象.成員函數,調用函數並訪問對象中的成員變量
stu.tellMe()

# 通過對象.屬性,直接訪問對象中的屬性
print(f"大家好,我是{stu.name},年齡{stu.age}")

# 通過對象直接訪問對象的屬性,並給屬性賦值
stu.name = "齊天大聖"
print(stu.name)  # 齊天大聖

"""
輸出結果:
大家好,我是孫悟空,年齡18
大家好,我是孫悟空,年齡18
齊天大聖


可以看到都可以訪問到。
"""

2)屬性被私有化情況:

# 2.屬性私有化的時候
class Student():
    def __init__(self, name, age):
        self.name = name
        # 私有化age屬性
        self.__age = age

    def tellMe(self):
        print(f"大家好,我是{self.name},年齡{self.__age}")

# 創建對象
stu = Student("孫悟空", 18)

# 通過對象.成員函數,調用函數並訪問對象中的成員變量
# 可以訪問當對象中的私有成員變量
# 輸出:大家好,我是孫悟空,年齡18
stu.tellMe()

# 通過對象.屬性,直接訪問對象中的屬性
# 報錯:
# AttributeError: 'Student' object has no attribute '__age'
# print(f"大家好,我是{stu.name},年齡{stu.__age}")

# 直接通過對象.屬性,修改成員變量屬性值
# 可以
stu.name = "齊天大聖"
print(stu.name)  # 齊天大聖
# 結果:大家好,我是齊天大聖,年齡18
stu.tellMe()

# 直接通過對象.屬性,修改私有成員變量屬性值
# 輸出了結果
stu.__age = 30
print(stu.__age)  # 30
# 結果:大家好,我是齊天大聖,年齡18
# 但是通過調用對象中的方法,查看對象的私有屬性
# 結果並沒有變化,還是18
stu.tellMe()

"""
但是我們通過__dict__屬性查看stu對象的所有成員:
{'name': '齊天大聖', '_Student__age': 18, '__age': 30}
可以看到我們並沒有修改原有的屬性'_Student__age': 18,
而是給stu對象新添加了一個屬性 '__age': 30。
所以說我們並沒有對私有成員變量age重新賦值成功。
只有 '_Student__age': 18為什麼指的是私有變量的age,
我們下面的私有成員原理就會講解。
"""
print(stu.__dict__)

說明:雙下劃線開頭的屬性,是對象的隱藏屬性,隱藏屬性只能在類的內部訪問,無法通過對象訪問

2、屬性私有化工作原理

class Student():
    def __init__(self, name, age):
        self.name = name
        # 私有化成員屬性
        self.__age = age

    def tellMe(self):
        print(f"大家好,我是{self.name},年齡{self.__age}")


"""
其實隱藏(私有化)屬性只不過是Python自動為屬性改了一個名字
實際上是將名字修改為了,_類名__屬性名 
比如 __age -> _Student__name,
也就是在原有的變量名前邊加上了 _類名。
"""

# 創建對象
stu = Student("孫悟空", 18)
# 結果:大家好,我是孫悟空,年齡18
stu.tellMe()

# 直接通過對象.屬性,修改對象的私有成員變量
# 通過下面的打印結果可以看到,修改成功了
stu._Student__age = 30
# 結果:大家好,我是孫悟空,年齡30
stu.tellMe()

總結:

Python中,並沒有 真正意義私有,實際是對成員變量的名稱做了一些特殊處理,使得外界無法訪問到。

所以定義為雙下劃線__開頭的屬性,實際上依然可以在外部訪問。

但平時我們不建議這麼訪問私有屬性。

3、定義成員變量的標識符規範

  • xxx:普通變量。
  • _xxx:使用_開頭的屬性為受保護的變量,沒有特殊需要不要修改。
  • __xxx:使用雙_開頭的屬性私有變量,在外界不能直接被訪問。
  • __xxx__:系統的內置變量。
  • xx_:單後置下劃線,用於避免與Python關鍵詞的衝突。

示例:

class Student():
    def __init__(self, name, age, gender):
        # 在變量的前面添加一個下劃線,屬於特殊變量,則認為該變量受保護的
        self._name = name

        # 在變量的前添加雙下劃線。表示定義私有變量。
        # 特點:對象不能直接使用
        self.__age = age

        # 在變量的前後各添加兩個下劃線,屬於特殊變量,一般認為這種變量都是系統的內置變量或者內置函數
        # 特點:在類的外面也可以直接訪問,但不建議使用
        self.__gender__ = gender


stu = Student("唐僧", 66, "男")
print(stu._name)  # 唐僧
print(stu.__gender__)  # 男

4、私有成員變量的獲取和設置方式

(1)方式:

如何獲取(修改)對象中的私有屬性?

需要提供一個getter()setter()方法使外部可以訪問到屬性。

  • getter()方法用來獲取對象中的指定屬性。
    getter()方法的規範命名為get_屬性名

  • setter()方法用來設置對象中的指定屬性。
    setter()方法的規範命名為set_屬性名

(2)格式:

getter()方法:會有一個返回值。

    def get_xxx(self):
        return self.屬性名

setter()方法:沒有返回值,但是方法多一個參數。

def set_xxx(self, 形參名稱):
            self.屬性名 = 形參名稱的值

(3)示例:

class Student():
    def __init__(self, name, age):
        # 普通變量
        self.name = name
        # 私有變量
        self.__age = age

    def tellMe(self):
        print(f"大家好,我是{self.name},年齡{self.__age}")

    def get_name(self):
        """
            get_name()用來獲取對象的name屬性
        """
        return self.name

    def set_name(self, name):
        """
            set_name()用來設置對象的name屬性
        """
        self.name = name

    def get_age(self):
        return self.__age

    def set_age(self, age):
        if age > 0:
            self.__age = age


# 創建對象
stu = Student("孫悟空", 18)
# 輸出結果:大家好,我是孫悟空,年齡18
stu.tellMe()

# 修改屬性值
stu.set_name("齊天大聖")
stu.set_age(30)

# 打印屬性值
# 結果都是:大家好,我是齊天大聖,年齡30
stu.tellMe()
print(f"大家好,我是{stu.get_name()},年齡{stu.get_age()}")

"""
私有屬性只能在類內部使用,對象不能直接使用,但是我們可以通過在類內部定義公有方法對私有屬性進行調用或修改,然後對象在調用這個公有方法使用
"""

(4)總結:

  • 不管是普通方法和私有方法都可以使用getter()方法和setter()方法來獲取或設置屬性值,我們日常開發中也經常是這麼編程的。
  • 使用封裝,確實增加了類的定義的複雜程度,但是它也確保了數據的安全性。
  • 增加了getter()方法和setter()方法,很好的控制的屬性是否是只讀的,
    如果希望屬性是只讀的,則可以直接去掉setter()方法,
    如果希望屬性不能被外部訪問,則可以直接去掉getter()方法。
  • 使用setter()方法設置屬性,可以增加數據的驗證,確保數據的值是正確的,如:
        def set_age(self, age):
            if age > 0:
                self.__age = age
    
  • 同理使用getter()方法獲取屬性時,或者使用setter()方法設置屬性時,可以在讀取屬性和修改屬性的同時,對數據做一些其他的處理。