一日一技:在什么情况下使用@property比较好?

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 一日一技:在什么情况下使用@property比较好?

摄影:产品经理干丝、火腿丝、香芹

我在2016年的时候,写过一篇文章,介绍@property装饰器。4年过去了,本来以为这个装饰器使用起来应该是很自然的事情,但还是有同学不知道在什么场景下可以使用它。

他们是这样说的:

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装饰器根本没有任何存在的必要,代码完全可以进一步简化:

class People:
    def __init__(self, name):
        self.name = name
        self.work = '还没有找到工作'

运行结果完全一样:

那么,使用@property装饰器的意义在哪里呢?

的确,在上面的例子里,@property装饰器没有任何存在的必要,因为这里读取一个对象的属性,仅仅是“返回数据”而已。但有些情况下,不仅仅要读取,还要计算。

我举一个例子,不知道你有没有这样的经历,你刚刚看了一眼手机,发现现在时间是23:10分。30秒以后,你朋友碰巧问你多少点了,你立刻回答:23:10分。他一看手表,还真是。于是惊呼,你怎么不看表就知道时间?

例如我们现在要实现一个ProxyProvider类,它读取 Redis,获取最新的代理 IP,然后随机返回一条。另外有一个程序,会增加新的代理 IP 到 Redis 中。但频率不高。

所以,ProxyProvider这个类,不需要每次获取 IP 的时候都读取数据库,每小时读取一次就可以了。如果不用@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:
            self.pool = self.get_all_proxies_from_redis()
        return random.choice(self.pool)

如果你经常看 Java 代码,你会发现大量的这种get_xxxset_xxx的写法。

于是,调用的时候,要这样调用:

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:
            self.pool = self.get_all_proxies_from_redis()
        return random.choice(self.pool)

于是读取的时候,这样写:

provider = ProxyProvider()
provider.proxy  # 注意这里不加括号

我们可以看到,整体代码逻辑是一样的,代码里并没有精简。不过在调用的时候,前者是调用一个方法,后者是读取一个属性。

同理,如果要修改数据,不使用@property的时候,需要实现一个set_xxx方法。但是使用了@property装饰一个方法,也可以在设置数据的时候实现一些内部逻辑,例如:

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:
            self.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)

而对于调用者来说,这些复杂的检查逻辑都是透明的:

provider = ProxyProvider()
provider.proxy = '123.45.67.89'

对于习惯于 Java 的人来说,他们可能喜欢显式写出get_xxxset_xxx方法。但是对于习惯 Python 的人来说,我觉得使用@property会让代码的可读性更好。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
2月前
|
存储 Python
property-data.
【10月更文挑战第14天】
27 1
|
Java 数据库连接 mybatis
There is no getter for property named ‘null‘ in ‘class
There is no getter for property named ‘null‘ in ‘class
193 0
There is no getter for property named ‘null‘ in ‘class
Property ‘sqlSessionFactory‘ or ‘sqlSessionTemplate‘ are required
Property ‘sqlSessionFactory‘ or ‘sqlSessionTemplate‘ are required
448 0
|
JavaScript 前端开发
Bean property属性说明
                      来自为知笔记(Wiz)
1292 0