实现数据模型或类型系统

想定义各种类型的数据结构,但是要对允许分配给某些属性的值实施约束。在此问题中,基本上要面对某些实例属性的设置进行检查或声明。 为此,需要基于每个属性自定义属性的设置,此时应该使用描述符。一般通过类及类的继承系统来实现,如下示例:

# Base class. Uses a descriptor to set a value
class Descriptor:
    def __init__(self, name=None, **opts):
        self.name = name
        for key, value in opts.items():
            setattr(self, key, value)
    
    def __set__(self, instance, value):
        instance.__dict__[self.name] = value


# Descriptor for enforcing types
class Typed(Descriptor):
    expected_type = type(None)

    def __set__(self, instance, value):
        if not isinstance(value, self.expected_type):
            raise TypeError('expected ' + str(self.expected_type))
        super().__set__(instance, value)


# Descriptor for enforcing values
class Unsigned(Descriptor):
    def __set__(self, instance, value):
        if value < 0:
            raise ValueError('Expected >= 0')
        super().__set__(instance, value)


class MaxSized(Descriptor):
    def __init__(self, name=None, **opts):
        if 'size' not in opts:
            raise TypeError('missing size option')
        super().__init__(name, **opts)

    def __set__(self, instance, value):
        if len(value) >= self.size:
            raise ValueError('size must be < ' + str(self.size))
        super().__set__(instance, value)


class Integer(Typed):
    expected_type = int


class UnsignedInteger(Integer, Unsigned):
    pass


class Float(Typed):
    expected_type = float


class UnsignedFloat(Float, Unsigned):
    pass


class String(Typed):
    expected_type = str


class SizedString(String, MaxSized):
    pass


class Stock:
    # Specify constraints
    name = SizedString('name', size=8)
    shares = UnsignedInteger('shares')
    price = UnsignedFloat('price')
    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price


>>> s = Stock('ACME', 50, 91.1) 
>>> s.name
'ACME'
>>> s.shares = 75
>>> s.shares = -10
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "example.py", line 17, in __set__
        super().__set__(instance, value) 
    File "example.py", line 23, in __set__
        raise ValueError('Expected >= 0') 
ValueError: Expected >= 0
>>> s.price = 'a lot'
Traceback (most recent call last):
    File "<stdin>", line 1, in <module> 
    File "example.py", line 16, in __set__
        raise TypeError('expected ' + str(self.expected_type)) 
TypeError: expected <class 'float'>
>>> s.name = 'ABRACADABRA'
Traceback (most recent call last):
    File "<stdin>", line 1, in <module> 
    File "example.py", line 17, in __set__
        super().__set__(instance, value) 
    File "example.py", line 35, in __set__
        raise ValueError('size must be < ' + str(self.size)) 
ValueError: size must be < 8
>>>

另一种实现方式是使用类装饰器,如下:

def check_attributes(**kwargs):
    def decorate(cls):
        for key, value in kwargs.items():
            if isinstance(value, Descriptor):
                value.name = key
                setattr(cls, key, value)
            else:
                setattr(cls, key, value(key))
        return cls
    return decorate


# Example
@check_attributes(name=SizedString(size=8), shares=UnsignedInteger,
price=UnsignedFloat)
class Stock:
    def __Init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

第三种方式是使用元类,如下:

# A metaclass that applies checking
class checkedmeta(type):
    def __new__(cls, clsname, bases, methods):
        # Attach attribute names to the descriptors
        for key, value in methods.items():
            if isinstance(value, Descriptor):
                value.name = key
        return type(cls, clsname, bases, methods)


# Example
class Stock(metaclass=checkedmeta):
    name = SizedString(size=8)
    shares = UnsignedInteger() 
    price = UnsignedFloat()
    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

本篇博文包含Python编程中的很多高级技巧和语言系统,如描述符,mixin类,super()的多重继承,类装饰器,元类等。这也是Python进阶中必须跨过的门槛。
· 首先,在Descriptor类中引入了_get_, _set_, __delete__等实现描述符协议的类方法。这在包含该类示例的类属性中使用属性访问时使用。

  • 整个类型继承包含了mixin,如Unsigned和MaxSized被分别引入在UnsignedFloat和SizedString类中。
  • 所有的__init__方法都引入opts的关键字参数,然后使用super()代理这些参数的初始化工作
  • 使用类装饰器或元类通常对于简化用户的规范很有用。 在这些示例中,用户不再需要多次键入属性名称。
  • 类装饰器和元类的代码只需扫描类字典以查找描述符。 找到后,它们仅根据键值填写描述符的name。

最后:在所有方法中,类装饰器解决方案都可以提供最大的灵活性和完整性。 一方面,它不依赖任何高级机制,例如元类。 其次,装饰器是可以很容易地根据需要从类定义中添加或删除。 例如,在装饰器中,可能有一个选项可以完全省略所添加的检查。 这些可能使检查成为可以根据需求打开或关闭(例如debug还是production模式)。

最后,类装饰器方法也可以代替mixin类,多重继承和对super()函数的棘手使用:

# Base class. Uses a descriptor to set a value
class Descriptor:
    def __init__(self, name=None, **opts):
        self.name = name
        for key, value in opts.items():
            setattr(self, key, value)

    def __set__(self, instance, value):
        instance.__dict__[self.name] = value


# Decorator for applying type checking
def Typed(expected_type, cls=None):
    if cls is None:
        return lambda cls: Typed(expected_type, cls)
    
    super_set = cls.__set__
    def __set__(self, instance, value):
        if not isinstance(value, expected_type):
            raise TypeError('expected ' + str(expected_type))
        super_set(self, instance, value)
    cls.__set__ = __set__
    return cls


# Decorator for unsigned values
def Unsigned(cls):
    super_set = cls.__set__
    def __set__(self, instance, value):
        if value < 0:
            raise ValueError('Expected >= 0')
        super_set(self, instance, value)
    cls.__set__ = __set__
    return cls


# Decorator for allowing sized values
def MaxSized(cls):
    super_init = cls.__init__
    def __init__(self, name=None, **opts):
        if 'size' not in opts:
            raise TypeError('missing size option')
        super_init(self, name, **opts)
    cls.__init__ = __init__

    super_set = cls.__set__
    def __set__(self, instance, value):
        if len(value) >= self.size:
            raise ValueError('size must be < ' + str(self.size))
        super_set(self, instance, value)
    cls.__set__ = __set__
    return cls


# Specialized descriptors
@Typed(int)
class Integer(Descriptor):
    pass


@Unsigned
class UnsignedInteger(Integer):
    pass


@Typed(float)
class Float(Descriptor):
    pass


@Unsigned
class UnsignedFloat(Float):
    pass


@Typed(str)
class String(Descriptor):
    pass


@MaxSized
class SizedString(String):
    pass

上面例子中,所有的装饰器函数都是将cls原先的__init__或__set__方法改写,并重新赋值。
此替代方案中定义的类以与之前完全相同的方式工作(早期示例代码均未更改),只是所有内容的运行速度更快。 例如,设置类型属性的简单时序测试表明,类装饰器方法的运行速度比使用mixins的方法快100%。

posted @ 2019-12-29 23:36  Jeffrey_Yang  阅读(214)  评论(0编辑  收藏  举报