Others

描述符

"""
某日,假设你需要一个类,来记录数学考试的分数。但是稍后你就发现了问题:分数为负值是没有意义的。

# class Score:
#     def __init__(self, math):
#         if math < 0:
#             raise ValueError('math score must >= 0')
#         self.math = math

但这样也没解决问题,因为分数虽然在初始化时不能为负,但后续修改时还是可以输入非法值:幸运的是,有内置装饰器 @property 可以解决此问题。
"""
class Score:
    def __init__(self, math):
        self.math = math

    @property
    def math(self):
        # self.math 取值
        return self._math

    @math.setter
    def math(self, value):
        # self.math 赋值
        if value < 0:
            raise ValueError('math score must >= 0')
        self._math = value

score = Score(90)
score.math =34
score._math   # 34
# 上面关于赋值检查的 NonNegative 已经展示描述符的其中一种用途了:托管属性并复用代码,保持简洁。


class NonNegative:
    # 注意这里
    # __init__ 也没有了
    def __set_name__(self, owner, name):
        self.name = name

    def __get__(self, instance, owner=None):
        return instance.__dict__.get(self.name)

    def __set__(self, instance, value):
        if value < 0:
            raise ValueError(f'{self.name} score must >= 0')
        instance.__dict__[self.name] = value


class Score:
    # NonNegative() 不需要带参数以规定属性名了
    math = NonNegative()

    def __init__(self, math):
        self.math = math

# score = Score(90)
# score.math =34
# score.math=-1
# ValueError: math score must >= 0

Cache

class Cache:
    """缓存描述符"""
    def __init__(self, func):
        self.func = func
        self.name = func.__name__

    def __get__(self, instance, owner=None):
        instance.__dict__[self.name] = self.func(instance)
        return instance.__dict__[self.name]


from time import sleep

class Foo:
    @Cache
    def bar(self):
        sleep(5)
        return 'Just sleep 5 sec...'

The normal built-in cache

from functools import lru_cache
@lru_cache(maxsize=None)
def fib(n):
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)

Validator

from abc import ABC, abstractmethod

class Validator(ABC):
    """验证器抽象基类"""
    def __set_name__(self, owner, name):
        self.private_name = '_' + name

    def __get__(self, instance, owner=None):
        return getattr(instance, self.private_name)

    def __set__(self, instance, value):
        self.validate(value)
        setattr(instance, self.private_name, value)

    @abstractmethod
    def validate(self, value):
        pass
        
        
class OneOf(Validator):
    """字符串单选验证器"""
    def __init__(self, *options):
        self.options = set(options)

    def validate(self, value):
        if value not in self.options:
            raise ValueError(f'Expected {value!r} to be one of {self.options!r}')

class Number(Validator):
    """数值类型验证器"""
    def validate(self, value):
        if not isinstance(value, (int, float)):
            raise TypeError(f'Expected {value!r} to be an int or float')
            
            
            

class Component:
    kind     = OneOf('wood', 'metal', 'plastic')
    quantity = Number()

    def __init__(self, kind, quantity):
        self.kind     = kind
        self.quantity = quantity
        
        
# Component('abc', 100) # ValueError                                
c = Component('wood', 100)
c.kind  #'wood'

Last updated