Better Code: 使用property

introduction

@property 是Python的一个内置函数, 通过property, 我们可以获得更好的可读性/可维护性

  • 屏蔽细节, 对于外部使用者, 不需要知道更多的细节!
  • 可读性更好, 调用方只有简单直接的逻辑, 而对象实体中包含了具体处理逻辑
  • 可维护性更好, 细节不会被扩散到其他地方, 后续变更修改更容易

refactor

代码坏味道:

  • 当你发现调用处, 每个地方获取到返回值后, 都做了一模一样的处理;
  • 变更属性的格式/分隔符等细节, 发现需要修改多个地方代码
  • 调用者需要感知到属性的当前值[1, 2, 3](type: list) 以及原始值1,2,3(type: str)

使用property之后:

  • 所有调用处, 没有重复代码
  • 变更属性的格式/分隔符等细节, 只需要修改一个地方
  • 调用者只知道属性的当前值[1, 2, 3]

usage

思路: 将属性通用的处理内化, fat model对外屏蔽细节, 避免了使用者的冗余代码以及过多的认知负担;

  • side effect: 可以在set/get/delete 操作时, 额外进行一些处理, 例如校验, 做一些额外变更等(存在副作用)
  • adapter: 也可以作为类似adapter, 组合或者拼装出对调用者更为友好的做法
  • 封装/facade: 对于类/对象实例, 更多的是起到facade 模式的作用, 将更多的细节封装, 对外屏蔽, 只暴露有限的内容

1. 属性校验

class Student(object):

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be an integer!')
        if value < 0 or value > 100:
            raise ValueError('score must between 0 ~ 100!')
        self._score = value

2. 限制只读

class Student(object):

    @property
    def birth(self):
        return self._birth

3. 拼装组合出额外的属性

class Staff:
    @property
    def email(self):
        return f'{self.username}@{self.domain}'

4. 类型/格式转换

下面示例是django 中的一种用法, 在db中字段名为maintainers, 是一个字符串, 但是对于模型使用者, maintainers是一个列表, 通过property, 屏蔽了字段类型/分隔符等细节

class API(models.Model):
      _maintainers = models.CharField(db_column='maintainers', max_length=1024)
      @property
      def maintainers(self) -> List[str]:
          if not self._maintainers:
              return []
          return self._maintainers.split(";")

      @maintainers.setter
      def maintainers(self, data: List[str]):
          self._maintainers = ";".join(data)

或者:

class A():
    @property
    def data(self):
        return json.loads(self._data)
    @data.setter
    def data(self, d):
        self._data = json.dumps(d)