2016年写了一篇介绍@property装饰器的文章。四年过去了,本以为使用这个装饰器是理所当然的事情,但是有的同学还是不知道它可以用在哪些场景下。他们是这样说的:classPeople:def__init__(self,name):self.name=nameself._work='Nojobfoundyet'@propertydefwork(self):returnself._work@work.setterdefwork(self,value):self。_work=value的运行效果如下图所示:但实际上这段代码中,根本不需要@property装饰器,代码还可以进一步简化:classPeople:def__init__(self,name):self.name=nameself.work='Nojobfound'结果完全一样:那么,使用@property装饰器的意义何在?确实,在上面的例子中,根本不需要@property装饰器,因为这里读取的是一个对象的属性,只是“返回数据”。但有些时候,不仅要阅读,还要计算。让我举一个例子。不知道你有没有这样的经历。你刚看了一眼手机,发现现在时间是23:10。30秒后,你的朋友恰好问你多少分钟,你马上回答:23:10分钟。他看了看表,果然如此。于是我惊呼,不看表怎么知道时间?比如我们现在要实现一个ProxyProvider类,读取Redis,获取最新的代理IP,随机返回一个。还有一个程序会向Redis添加新的代理IP。但是频率不高。因此,ProxyProvider类不需要每次获取IP都去读数据库,一个小时只需要读一次。如果你不使用@property装饰器,你可能会写这样的代码:.last_update_time>3600ornotself.pool:selfself.pool=self.get_all_proxies_from_redis()returnrandom.choice(self.pool)如果你经常看java代码,会发现很多这种get_xxx,set_xxx的写法。因此,调用时,这样调用:provider=ProxyProvider()provider.get_proxy()如果使用@property,代码可以改写为:importtimeimportrandomclassProxyProvider:def__init__(self):self.pool=[]self.last_update_time=0@propertydefproxy(self):now=time.time()ifnow-self.last_update_time>3600ornotself.pool:selfself.pool=self.get_all_proxies_from_redis()returnrandom.choice(self.pool)所以读的时候这样写:provider=ProxyProvider()provider.proxy#注意这里没有括号。我们可以看到整体的代码逻辑是一样的,并没有简化代码。但是在调用的时候,前者是调用一个方法,后者是读取一个属性。同样,如果要修改数据,在不使用@property的时候,需要实现一个set_xxx方法。但是使用@property修饰方法也可以在设置数据时实现一些内部逻辑,例如:importtimeimportrandomclassProxyProvider:def__init__(self):self.pool=[]self.special_ip=set()self.last_update_time=0@propertydefproxy(self):now=time.time()ifnow-self.last_update_time>3600ornotself.pool:selfself.pool=self.get_all_proxies_from_redis()returnrandom.choice(self.pool+list(self.special))@proxy.setterdefproxy(self,value):ifnotvalue.startswith('http'):proxy=f'http://{ip}'ifproxyinself.special_ip:returnsself.special_ip.add(proxy)对于调用者来说,这些复杂的检查逻辑透明:provider=ProxyProvider()provider.proxy='123.45.67.89'对于用惯Java的人来说,可能喜欢显式写get_xxx和set_xxx方法。但是对于用惯了Python的人来说,我觉得使用@property会让代码的可读性更好。
