codemee

Posted on

# 這是什麼妖術？Python 的屬性 (property) 運作原理

## 使用 @property 裝飾器建立屬性

``````>>> time.time()
1646532639.2636657
>>>
``````

``````>>> class C:
...     def __init__(self):
...         self.start = time.time()
...     @property
...     def age(self):
...         return int(time.time() - self.start)
...     @age.setter
...     def age(self, new_age):
...         self.start = time.time() - new_age
...
>>>
``````

• `__init__()` 中將建立物件的時間記錄下來, 之後就可以根據這個時間點計算物件的存活時間。
• `@porperty` 裝飾器則是將下一列的 `age()` 方法變成 `age` 屬性, 當我們透過 `.` 運算器讀取 `age` 屬性時, 就會自動叫用它。
• `@age.setter` 裝飾器則是讓下一列的 `age()` 變成設定 `age` 屬性的方法, 當利用指派敘述設定 `age` 屬性時, 就會自動叫用它。

``````>>> c = C()
>>> c.age
6
>>> c.age
9
>>>
``````

``````>>> c.age = 0
>>> c.age
1
``````

``````>>> C.__dict__['age']
<property object at 0x0000018CD4B64040>
>>> vars(C)['age']
<property object at 0x0000018CD4B64040>
>>>
``````

## 使用描述器 (descriptor) 建立屬性

``````>>> class Age:
...     def __get__(self, obj, objType=None):
...         return int(time.time() - obj.start)
...
>>>
``````

``````>>> class D:
...     age = Age()
...     def __init__(self):
...         self.start = time.time()
...
>>>
``````

``````>>> d = D()
>>> d.age
3
>>> d.age
4
>>> d.age
6
``````

`.` 運算器發現 `age` 是一個具備 `__get_()` 的描述器時, 不會直接把描述器當成運算結果, 而是叫用描述器的 `__get__()`, 以它的傳回值作為運算結果, 在本例中就會執行 `Age` 類別的 `__get__()`, 傳回物件的存活時間。

``````>>> class Age:
...     def __get__(self, obj, objType=None):
...         return int(time.time() - obj.start)
...     def __set__(self, obj, value):
...         obj.start = time.time() - value
...
``````

``````>>> class D:
...     age = Age()
...     def __init__(self):
...         self.start = time.time()
...
``````

``````>>> d = D()
>>> d.age
2
>>> d.age
9
>>> d.age = 0
>>> d.age
2
>>> d.age
3
>>>
``````

``````>>> class Age:
...     def __get__(self, obj, objType):
...         return int(time.time() - obj.start)
...
>>> class D:
...     age = Age()
...     def __init__(self):
...         self.start = time.time()
...
>>> d = D()
>>> d.age
5
>>>
``````

``````>>> d.__dict__
{'start': 1646556621.0307684}
>>> d.age = 0
>>> d.__dict__
{'start': 1646556621.0307684, 'age': 0}
>>> d.age
0
>>>
``````

``````>>> d1 = D()
>>> d1.age
7
>>>
``````

``````>>> class Age:
...     def __get__(self, obj, objType):
...         return int(time.time() - obj.start)
...     def __set__(self, obj, value):
...
>>> class D:
...     age = Age()
...     def __init__(self):
...         self.start = time.time()
...
>>> d = D()
>>> d.age
3
>>> d.age = 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in __set__
>>>
``````

## 使用 property 物件當描述器

``````>>> class E:
...     def __init__(self):
...         self.start = time.time()
...     def getAge(self):
...         return int(time.time() - self.start)
...     def setAge(self, new_age):
...         self.start = time.time() - new_age
...     age = property(getAge, setAge)
...
``````

`property` 建構方法中前兩個參數分別就是負責讀寫的方法, 執行結果如下：

``````>>> e = E()
>>> e.age
2
>>> e.age
3
>>> e.age
5
>>> e.age = 0
>>> e.age
2
>>>
``````

`property` 類別提供有 `getter()``setter()` 可以單獨設定 `__get__()``__set__()` 要叫用的方法, 所以你也可以把工作分段, 像是這樣：

``````>>> class E:
...     def __init__(self):
...         self.start = time.time()
...     def getAge(self):
...         return int(time.time() - self.start)
...     def setAge(self, new_age):
...         self.start = time.time() - new_age
...     age = property(getAge)
...     age = age.setter(setAge)
...
>>>
``````

``````>>> e = E()
>>> e.age
2
>>> e.age
3
>>> e.age
4
>>> e.age = 0
>>> e.age
1
>>>
``````

## @property = 裝飾器 + 描述器

``````>>> class F:
...     def __init__(self):
...         self.start = time.time()
...     def age(self):
...         return int(time.time() - self.start)
...     age = property(age)
...     age_setter = age.setter
...     def age(self, new_age):
...         self.start = time.time() - new_age
...     age = age_setter(age)
...
``````

``````age = property(age)
...
age = age_setter(age)
``````

``````>>> class G:
...     def __init__(self):
...         self.start = time.time()
...     @property
...     def age(self):
...         return int(time.time() - self.start)
...     @age.setter
...     def age(self, new_age):
...         self.start = time.time() - new_age
...
>>>
``````