在什么情況下使用@property比較好?
我在2016年的時候,寫過一篇文章,介紹@property裝飾器。4年過去了,本來以為這個裝飾器使用起來應(yīng)該是很自然的事情,但還是有同學(xué)不知道在什么場景下可以使用它。
他們是這樣說的:
- class People:
- def __init__(self, name):
- self.name = name
- self._work = '還沒有找到工作'
- @property
- def work(self):
- return self._work
- @work.setter
- def work(self, value):
- self._work = value
運行效果如下圖所示:
但實際上,這段代碼里面,@property裝飾器根本沒有任何存在的必要,代碼完全可以進(jìn)一步簡化:
- class People:
- def __init__(self, name):
- self.name = name
- self.work = '還沒有找到工作'
運行結(jié)果完全一樣:
那么,使用@property裝飾器的意義在哪里呢?
的確,在上面的例子里,@property裝飾器沒有任何存在的必要,因為這里讀取一個對象的屬性,僅僅是“返回數(shù)據(jù)”而已。但有些情況下,不僅僅要讀取,還要計算。
我舉一個例子,不知道你有沒有這樣的經(jīng)歷,你剛剛看了一眼手機,發(fā)現(xiàn)現(xiàn)在時間是23:10分。30秒以后,你朋友碰巧問你多少點了,你立刻回答:23:10分。他一看手表,還真是。于是驚呼,你怎么不看表就知道時間?
例如我們現(xiàn)在要實現(xiàn)一個ProxyProvider類,它讀取 Redis,獲取最新的代理 IP,然后隨機返回一條。另外有一個程序,會增加新的代理 IP 到 Redis 中。但頻率不高。
所以,ProxyProvider這個類,不需要每次獲取 IP 的時候都讀取數(shù)據(jù)庫,每小時讀取一次就可以了。如果不用@property裝飾器,你可能會這樣寫代碼:
- import time
- import random
- class ProxyProvider:
- def __init__(self):
- self.pool = []
- self.last_update_time = 0
- def get_proxy(self):
- now = time.time()
- if now - self.last_update_time > 3600 or not self.pool:
- selfself.pool = self.get_all_proxies_from_redis()
- return random.choice(self.pool)
如果你經(jīng)???Java 代碼,你會發(fā)現(xiàn)大量的這種get_xxx、set_xxx的寫法。
于是,調(diào)用的時候,要這樣調(diào)用:
- provider = ProxyProvider()
- provider.get_proxy()
如果用@property,那么代碼可以改寫為:
- import time
- import random
- class ProxyProvider:
- def __init__(self):
- self.pool = []
- self.last_update_time = 0
- @property
- def proxy(self):
- now = time.time()
- if now - self.last_update_time > 3600 or not self.pool:
- selfself.pool = self.get_all_proxies_from_redis()
- return random.choice(self.pool)
于是讀取的時候,這樣寫:
- provider = ProxyProvider()
- provider.proxy # 注意這里不加括號
我們可以看到,整體代碼邏輯是一樣的,代碼里并沒有精簡。不過在調(diào)用的時候,前者是調(diào)用一個方法,后者是讀取一個屬性。
同理,如果要修改數(shù)據(jù),不使用@property的時候,需要實現(xiàn)一個set_xxx方法。但是使用了@property裝飾一個方法,也可以在設(shè)置數(shù)據(jù)的時候?qū)崿F(xiàn)一些內(nèi)部邏輯,例如:
- import time
- import random
- class ProxyProvider:
- def __init__(self):
- self.pool = []
- self.special_ip = set()
- self.last_update_time = 0
- @property
- def proxy(self):
- now = time.time()
- if now - self.last_update_time > 3600 or not self.pool:
- selfself.pool = self.get_all_proxies_from_redis()
- return random.choice(self.pool + list(self.special))
- @proxy.setter
- def proxy(self, value):
- if not value.startswith('http'):
- proxy = f'http://{ip}'
- if proxy in self.special_ip:
- return
- self.special_ip.add(proxy)
而對于調(diào)用者來說,這些復(fù)雜的檢查邏輯都是透明的:
- provider = ProxyProvider()
- provider.proxy = '123.45.67.89'
對于習(xí)慣于 Java 的人來說,他們可能喜歡顯式寫出get_xxx和set_xxx方法。但是對于習(xí)慣 Python 的人來說,我覺得使用@property會讓代碼的可讀性更好。