前言
- 前面講到實例屬性的時候,我們可以通過 實例對象.實例屬性 來訪問對應的實例屬性
- 但這種做法是不建議的,因為它破壞了類的封裝原則
- 正常情況下,實例屬性應該是隱藏的,只允許通過類提供的方法來間接實現對實例屬性的訪問和操作
class PoloBlog: # 構造方法 def __init__(self, name): self.name = name blog = PoloBlog("小菠蘿") # 破壞了封裝原則 print(blog.name) blog.name = "啊?" print(blog.name) # 輸出結果 小菠蘿 啊?
getter、setter 方法
- 不破壞類封裝原則的基礎上,操作實例屬性
- 寫過 java 的話應該知道,java 的類可以自動生成對屬性的操作方法,一個是 get,另一個是 set(一般稱為 getter、setter 方法)
- python 中雖然不能自動生成,但也可以自己寫哦
class PoloBlog: # 構造方法 def __init__(self, name): self.name = name # set 屬性的方法【setter】 def setName(self, name): self.name = name # get 屬性的方法【getter】 def getName(self): return self.name blog = PoloBlog("小菠蘿") # 獲取 blog 實例對象的 name 實例屬性 print(blog.getName()) # 設置 name 實例屬性 blog.setName("新的小菠蘿") print(blog.getName()) # 輸出結果 小菠蘿 新的小菠蘿
這樣跟 java 的寫法就差不多了,但還是有點麻煩
property() 方法的誕生
可以實現在不破壞類封裝原則的前提下,讓開發者依舊使用 對例對象.屬性 的方式操作類中的屬性
基本使用格式
屬性名 = property(fget=None, fset=None, fdel=None, doc=None)
- fget:用於獲取屬性的方法
- fset:用於設置屬性的方法
- fdel:用於刪除屬性的方法
- doc:屬性的說明文檔字符串
代碼栗子
# property() 函數 class PoloBlog: # 構造函數 def __init__(self, name): self.__name = name # setter def setName(self, name): self.__name = name # getter def getName(self): return self.__name # del def delName(self): self.__name = "xxx" # property() name = property(getName, setName, delName, "小菠蘿測試筆記") # 調用說明文檔 # help(PoloBlog.name) print(PoloBlog.name.__doc__) blog = PoloBlog("小菠蘿") # 自動調用 getName() print(blog.name) # 自動調用 setName() blog.name = "新的小菠蘿" print(blog.name) # 自動調用 delName() del blog.name print(blog.name) # 輸出結果 小菠蘿測試筆記 小菠蘿 新的小菠蘿 xxx
getName return 的是私有屬性 __name,注意不是 name,不然會陷入死循環
注意
property() 方法的四個參數都是默認參數,可以不傳參
# property() 函數 class PoloBlog: # 構造函數 def __init__(self, name, age): self.__name = name self.__age = age # setter name def setName(self, name): self.__name = name # getter name def getName(self): return self.__name # del name def delName(self): self.__name = "xxx" # setter age def setAge(self, age): self.__age = age # getter age def getAge(self): return self.__age # property() name = property(getName, setName, delName, "小菠蘿測試筆記") # 沒有 fdel、doc age = property(getAge, setAge) blog = PoloBlog("小菠蘿", 14) print(blog.age) blog.age = "24" print(blog.age) del blog.age print(blog.age) # 輸出結果 14 24 del blog.age AttributeError: can't delete attribute
因為 property() 沒有傳 fdel 方法,所以無法刪除屬性,它是一個可讀寫,不可刪的屬性
其他傳參解析
name = property(getName) # name 屬性可讀,不可寫,也不能刪除 name = property(getName, setName,delName) #name屬性可讀、可寫、也可刪除,就是沒有說明文檔
@property
- 是一個裝飾器,相當於 getter 裝飾器
- 可以使用 @property 來創建只讀屬性,將一個實例方法變成一個相同名稱的只讀實例屬性,這樣可以防止屬性被修改
代碼栗子
# @property class PoloBlog: def __init__(self, name): self.__name = name @property def name(self): return self.__name blog = PoloBlog("小菠蘿") print(blog.name) blog.name = "test" # 輸出結果 小菠蘿 blog.name = "test" AttributeError: can't set attribute
name 是一個只讀屬性,不可寫,相當於 __name 私有屬性只有 getter 方法,沒有 setter 方法
等價寫法
class PoloBlog: def __init__(self, name): self.__name = name def getName(self): return self.__name name = property(getName) blog = PoloBlog("小菠蘿") print(blog.name)
那想給 __name 設置值怎么辦呢?
setter 裝飾器
語法格式
@方法名.setter def 方法名(self, value): self.__value = value ...
代碼栗子
# @setter class PoloBlog: def __init__(self, name): self.__name = name @property def name(self): return self.__name @name.setter def name(self, name): self.__name = name blog = PoloBlog("小菠蘿") # 打印屬性值 print(blog.name) # 修改屬性 blog.name = "新的小菠蘿" print(blog.name) # 輸出結果 小菠蘿 新的小菠蘿
deleter 裝飾器
和 setter 裝飾器差不多寫法
語法格式
@方法名.deleter def 方法名(self): ...
代碼栗子
class PoloBlog: def __init__(self, name): self.__name = name @property def name(self): return self.__name @name.setter def name(self, name): self.__name = name @name.deleter def name(self): print("刪除 __name") blog = PoloBlog("小菠蘿") # 打印屬性值 print(blog.name) # 修改屬性 blog.name = "新的小菠蘿" # 刪除屬性 del blog.name # 輸出結果 小菠蘿 刪除 __name
@property 踩坑
加了 @property 的方法相當於一個實例屬性,所以不能和其他實例屬性重名
錯誤代碼栗子
class A: def __init__(self): # 已經定義了 name 實例屬性 self.name = 2 # 這里相當於也定義了一個 name 實例屬性 @property def name(self): return self.name @name.setter def name(self, name): self.name = name a = A() print(a.name) # 輸出結果 Traceback (most recent call last): File "/Users/polo/Documents/pylearn/第四章:面向對象/17_實戰5.py", line 26, in <module> a = A() File "/Users/polo/Documents/pylearn/第四章:面向對象/17_實戰5.py", line 14, in __init__ self.name = 2 File "/Users/polo/Documents/pylearn/第四章:面向對象/17_實戰5.py", line 23, in name self.name = name File "/Users/polo/Documents/pylearn/第四章:面向對象/17_實戰5.py", line 23, in name self.name = name File "/Users/polo/Documents/pylearn/第四章:面向對象/17_實戰5.py", line 23, in name self.name = name [Previous line repeated 994 more times] RecursionError: maximum recursion depth exceeded
報錯翻譯:遞歸錯誤 超過最大遞歸深度
其實就是因為命名沖突導致了死循環
改下命名就好了
class A: def __init__(self): # 已經定義了 name 實例屬性 self.name = 2 # 這里相當於也定義了一個 name 實例屬性 @property def name_func(self): return self.name @name_func.setter def name_func(self, name): self.name = name a = A() print(a.name) # 輸出結果 2