1. 程式人生 > 其它 >Python之@property詳解及底層實現介紹

Python之@property詳解及底層實現介紹

@property的介紹

轉自:https://blog.csdn.net/weixin_42681866/article/details/83376484

前文

Python內建有三大裝飾器:@staticmethod(靜態方法)、@classmethod(類方法)、@property(描述符),其中靜態方法就是定義在類裡的函式,並沒有非要定義的必要;類方法則是在呼叫類屬性、傳遞類物件時使用;而@property則是一個非常好用的語法糖。@property最大的好處就是在類中把一個方法變成屬性呼叫,起到既能檢查屬性,還能用屬性的方式來訪問該屬性的作用

@property應用

讓我們先看下@property的應用,其功能1是可定義只讀屬性,也就是真正意義上的私有屬性(屬性前雙下劃線的私有屬性也是可以訪問的,具體參照這篇文章:

私有屬性真的是私有的嗎?)例項需求是定義類Person,具有年齡和姓名,要求年齡必須等於18,則程式碼如下:

class Person(object):
    def __init__(self, name, age=18):
        self.name = name
        self.__age = 18

    @property
    def age(self):
        return self.__age

    
xm = Person('xiaoming')  # 定義一個人名小明
print(xm.age)  # 結果為18
xm.age = -4	 # 報錯無法給年齡賦值
print(xm.age)

結果如下:

18
Traceback (most recent call last):
	xm.age = 18
AttributeError: can't set attribute

在python中定義只讀屬性非@property莫屬,如果細心留意大部分原始碼,都跑不了@property的身影。而定義只讀屬性也很簡單:以需要定義的屬性為方法名(上例age屬性定義為方法),其上裝飾內建裝飾器@property就ok了。

@property真正強大的是可以限制屬性的定義。往往我們定義類,希望其中的屬性必須符合實際,但因為在__ init __裡定義的屬性可以隨意的修改,導致很難實現。如我想實現Person類,規定每個人(即建立的例項)的年齡必須大於18歲,正常實現的話,則必須將屬性age設為只讀屬性,然後通過方法來賦值,程式碼如下:

class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.__age = 18

    @property
    def age(self):
        return self.__age

    def set_age(self, age):  # 定義函式來給self.__age賦值
        if age < 18:
            print('年齡必須大於18歲')
            return
        self.__age = age
        return self.__age

    
xm = Person('xiaoming', 20)    
print(xm.age)
print('----------')
xm.set_age(10)
print(xm.age)
print('----------')
xm.set_age(20)
print(xm.age)

為了便於區分結果,增加了分隔符,結果如下:

18
----------
年齡必須大於18歲
18
----------
20

可以看到,通過方法的確能限制輸入,但是不是每個人都記得方法名,有什麼簡化的方法呢?@property就能很輕鬆辦到,修改程式碼如下:

class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.__age = 18

    @property
    def age(self):
        return self.__age

    @age.setter
    def age(self, age):
        if age < 18:
            print('年齡必須大於18歲')
            return
        self.__age = age
        return self.__age


xm = Person('xiaoming', 20)
print(xm.age)
print('----------')
xm.age = 10
print(xm.age)
print('----------')
xm.age = 20
print(xm.age)

結果和上圖一致。兩段程式碼變化的內容:將set_age修改為age,並且在上方加入裝飾器@age.setter。這就是@property定義可訪問屬性的語法,即仍舊以屬性名為方法名,並在方法名上增加@屬性.setter就行了

@property實現原理

在開頭說過,@property是個描述符(decorator),實際上他本身是類,不信的話,可以將上述程式碼修改如下:

class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.__age = 18

    def get_age(self):  # 恢復用方法名來獲取以及定義
        return self.__age

    def set_age(self, age): 
        if age < 18:
            print('年齡必須大於18歲')
            return
        self.__age = age
        return self.__age

    
age = property(get_age, set_age)  # 增加property類

上述程式碼的執行結果和前面一致,將@property裝飾的屬性方法再次修改回定義方法名,然後再類的最下方,定義:屬性=property(get,set,del),這個格式是固定的,通過原始碼可以知道原因,property部分原始碼如下:

class property(object):
	def __init__(self, fget=None, fset=None, fdel=None, doc=None):
		"""
        Property attribute.
        
          fget
            function to be used for getting an attribute value
          fset
            function to be used for setting an attribute value
          fdel
            function to be used for del'ing an attribute
		class C(object):
            @property
            def x(self):
                "I am the 'x' property."
                return self._x
            @x.setter
            def x(self, value):
                self._x = value
            @x.deleter
            def x(self):
                del self._x
  		"""
 		pass	
     def __set__(self, *args, **kwargs):  # real signature unknown
       """ 
       Set an attribute of instance to value. 
       """
       	pass
     def __get__(self, *args, **kwargs):  # real signature unknown
	    """ 
	    Return an attribute of instance, which is of type owner.
        """
	    pass
     def __delete__(self, *args, **kwargs):  # real signature unknown
        """ 
        Delete an attribute of instance.
        """
        pass

為了不讓篇幅過長,同時影響到閱讀效果,這邊截取了部分,可以看到程式碼註釋裡寫了官方的@property用法,同時如果是用類來實現的話,必須得按__ init __ 的引數裡順序fget/fset/fdel依次填入get、set、del方法,因為都是預設引數,所以都可以省略不寫。

介紹下python的描述符,定義:如果一個類裡定義了__ set __ 、__ get __ 、__ delete __三個方法之一,同時給另一個類的屬性賦值為例項,那麼該類可以稱之為描述符。因為描述符的使用目前就python,所以瞭解下就行了。

總結

關於@property的介紹到此為止,@property裝飾器就是一個語法糖(語法糖指那些沒有給計算機語言新增新功能,而只是對人類來說更“甜蜜”的語法。 語法糖往往給程式設計師提供了更實用的編碼方式,有益於更好的編碼風格,更易讀。)。