『無為則無心』Python物件導向 — 51、私有成員變數(類中資料的封裝)

繁華似錦Fighting發表於2022-02-23

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()方法設定屬性時,可以在讀取屬性和修改屬性的同時,對資料做一些其他的處理。

相關文章