【问题标题】:Restricting attribute type with metaclass使用元类限制属性类型
【发布时间】:2012-05-10 02:41:22
【问题描述】:

我正在尝试使用 Python 进行元类编程,我想知道如何使用元类限制属性类型。 使用描述符很容易,但是元类呢?

这是一个简短的例子:

>>> class Image(Object):
    ...     height = 0
    ...     width = 0
    ...     path = '/tmp'
    ...     size = 0

    >>> img = Image()
    >>> img.height = 340
    >>> img.height
    340
    >>> img.path = '/tmp/x00.jpeg'
    >>> img.path
    '/tmp/x00.jpeg'
    >>> img.path = 320
    Traceback (most recent call last):
      ...
    TypeError

Python 版本是 2.7

【问题讨论】:

  • 我认为这仅用于教育,您并不打算将静态类型检查实际绑定到 Python。
  • 你的意思是使用元类来设置描述符吗?

标签: python metaclass


【解决方案1】:

只需覆盖元类中的__setattr__ 并在初始化期间检查每个属性的默认类型:

>>> class Meta(type):
    def __new__(meta, name, bases, dict):
        def _check(self, attr, value):
            if attr in self.defaults:
                if not isinstance(value, self.defaults[attr]):
                    raise TypeError('%s cannot be %s' % (attr, type(value)))
            else:                        
                self.defaults[attr] = type(value)

        def _setattr(self, attr, value):
            _check(self, attr, value)
            object.__setattr__(self, attr, value)

        cls = type.__new__(meta, name, bases, dict)
        # Set up default type for every attribute
        cls.defaults = {name: type(value) for name, value in dict.items()}
        cls.__setattr__ = _setattr
        return cls


>>> class Image(object):
    __metaclass__ = Meta
    height = 0
    width = 0
    path = '/tmp'
    size = 0


>>> i = Image()
>>> i.height = 240
>>> i.height
240
>>> i.size
0
>>> i.size = 7
>>> i.size
7
>>> i.path = '/tmp/subdir'
>>> i.path
'/tmp/subdir'
>>> i.path = 23
TypeError: path cannot be <type 'int'>

另一种(也许更优雅)的方法:

class MetaBase(object):
    def _check(self, attr, value):
        if attr in self.defaults:
            if not isinstance(value, self.defaults[attr]):
                raise TypeError('%s cannot be %s' % (attr, type(value)))
        else:
            self.defaults[attr] = type(value)
    def __setattr__(self, attr, value):
        self._check(attr, value)
        super(MetaBase, self).__setattr__(attr, value)

class Meta(type):
    def __new__(meta, name, bases, dict):
        cls = type.__new__(meta, name, (MetaBase,) + bases, dict)
        cls.defaults = {name: type(value) for name, value in dict.items()}
        return cls

class Image(object):
    __metaclass__ = Meta
    height = 0
    width = 0
    path = '/tmp'
    size = 0

行为和以前一样

【讨论】:

    【解决方案2】:

    覆盖元类中的__setattr__。请注意,您必须单独检查初始值(在您的示例中为height=0, path = '/tmp'):

    class RestrictAttrs(type):
        def __new__(mcs, name, bases, dct):
            def _checkattr(k, v):
                if k == 'path':
                    if not isinstance(v, str):
                        raise TypeError('path must be a str!')
    
            def _setattr(self, k, v):
                _checkattr(k, v)
                self.__dict__[k] = v
    
            # Check of initial values (optional)
            for k,v in dct.items():
                _checkattr(k, v)
    
            res = type.__new__(mcs, name, bases, dct)
            res.__setattr__ = _setattr
            return res
    
    class Image(object):
        __metaclass__ = RestrictAttrs
        path = '/tmp'
    
    i = Image()
    i.path = 32
    

    【讨论】:

      猜你喜欢
      • 2020-03-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多