如何编写在__init __()之后阻止创建新属性的元类?

wor*_*bin 7 python metaclass python-2.7

目前,我__setattr__在类' __init__()方法的末尾覆盖了类' (),以防止创建新属性 -

class Point(object):
    def __init__(self):
        self.x = 0
        self.y = 0
        Point.__setattr__ = self._setattr

    def _setattr(self, name, value):
        if not hasattr(self, name):
            raise AttributeError("'" + name + "' not an attribute of Point object.")
        else:
            super(Point, self).__setattr__(name, value)
Run Code Online (Sandbox Code Playgroud)

有没有办法避免手动覆盖__setattr__()并在元类的帮助下自动执行此操作?

我最近的是 -

class attr_block_meta(type):
    def __new__(meta, cname, bases, dctry):
        def _setattr(self, name, value):
            if not hasattr(self, name):
                raise AttributeError("'" + name + "' not an attribute of " + cname + " object.")
            object.__setattr__(self, name, value)

        dctry.update({'x': 0, 'y': 0})
        cls = type.__new__(meta, cname, bases, dctry)
        cls.__setattr__ = _setattr
        return cls

class ImPoint(object):
    __metaclass__ = attr_block_meta
Run Code Online (Sandbox Code Playgroud)

是否有更通用的方法来执行此操作,以便不需要对子类属性的先验知识?
基本上,dctry.update({'x': 0, 'y': 0})无论类属性的名称是什么,如何避免该行并使其工作?

PS - FWIW我已经评估了这些__slots__和namedtuple选项,发现它们缺乏我的需求.请不要将注意力集中在我用来说明问题的削减点()示例上; 实际的用例涉及更复杂的类.

shx*_*hx2 10

不要重新发明轮子.

实现该目的的两种简单方法(不直接使用元类)使用:

  1. namedtuple小号
  2. __slots__

例如,使用namedtuple(基于文档中的示例):

Point = namedtuple('Point', ['x', 'y'])
p = Point(11, 22)
p.z = 33  # ERROR
Run Code Online (Sandbox Code Playgroud)

例如,使用__slots__:

class Point(object):
    __slots__ = ['x', 'y']
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

p = Point(11,22)
p.z = 33  # ERROR
Run Code Online (Sandbox Code Playgroud)

  • @chepner:如果你想获得真正的技术,它实际上并不像普通的用户定义类那样使用`__slots__`.它有`__slots__ =()`(根本没有定义的属性)来防止创建`__weakref__`和`__dict__`; 实际存储是通过委托给`tuple`索引的`@ property`s完成的. (7认同)
  • FWIW,`__slots__`用于空间优化,而不是任意限制实例的可能属性.它还强加了其他一些限制,参见https://docs.python.org/2/reference/datamodel.html#slots.现在它在这里没有意义 - 一个点是一个完美的插槽用例,因为它具有很少的属性,通常是相当多的实例,并且作为"数据"对象的主要添加属性没有多大意义...... (5认同)
  • @ shx2正如我所提到的那样,将插槽用于一个简单的"数据"对象是有意义的 - 我想指出的是插槽不是用来限制动态的一种方式,而是作为一种空间优化权衡.无用的任意限制是非常不合理的. (4认同)

Mat*_*son 6

您不需要元类来解决此类问题.

如果你想一次创建的数据前面,然后把它是不可变的,我肯定会用一个namedtuple作为shx2建议.

否则,只需在类上定义允许字段的集合,并__setattr__检查您尝试设置的名称是否在允许的字段集合中.您不需要更改__setattr__部分方法的实现__init__- 它将在__init__以后工作​​的同时工作.如果您想阻止在给定类上改变/更改它们,请使用tuplefrozenset作为允许字段的数据结构.

class Point(object):
    _allowed_attrs = ("x", "y")

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __setattr__(self, name, value):
        if name not in self._allowed_attrs:
            raise AttributeError(
                "Cannot set attribute {!r} on type {}".format(
                    name, self.__class__.__name__))
        super(Point, self).__setattr__(name, value)

p = Point(5, 10)
p.x = 9
p.y = "some string"
p.z = 11  # raises AttributeError
Run Code Online (Sandbox Code Playgroud)

这很容易被分解为基类以供重用:

class RestrictedAttributesObject(object):
    _allowed_attrs = ()

    def __setattr__(self, name, value):
        if name not in self._allowed_attrs:
            raise AttributeError(
                "Cannot set attribute {!r} on type {}".format(
                    name, self.__class__.__name__))
        super(RestrictedAttributesObject, self).__setattr__(name, value)

class Point(RestrictedAttributesObject):
    _allowed_attrs = ("x", "y")

    def __init__(self, x, y):
        self.x = x
        self.y = y
Run Code Online (Sandbox Code Playgroud)

我不认为以这种方式锁定对象的允许属性会被认为是pythonic,并且它会导致需要额外属性的子类的一些复杂化(子类必须确保该_allowed_attrs字段具有适合它的内容) ).


Arn*_*ols 6

这对你的案子有意义吗?

from functools import wraps

class attr_block_meta(type):
    def __new__(meta, cname, bases, dctry):
        def _setattr(self, name, value):
            if not hasattr(self, name):
                raise AttributeError("'" + name + "' not an attibute of " + cname + " object.")
            object.__setattr__(self, name, value)

        def override_setattr_after(fn):
            @wraps(fn)
            def _wrapper(*args, **kwargs):
                cls.__setattr__ = object.__setattr__
                fn(*args, **kwargs)
                cls.__setattr__ = _setattr
            return _wrapper

        cls = type.__new__(meta, cname, bases, dctry)
        cls.__init__ = override_setattr_after(cls.__init__)
        return cls


class ImPoint(object):
    __metaclass__ = attr_block_meta
    def __init__(self, q, z):
        self.q = q
        self.z = z

point = ImPoint(1, 2)
print point.q, point.z
point.w = 3  # Raises AttributeError
Run Code Online (Sandbox Code Playgroud)

对"包装"的更多细节.

您可能需要更多地使用它来使其更优雅,但一般的想法是仅在调用init之后重写__setattr__ .

话虽如此,一个常见的方法是在object.__setattr__(self, field, value)内部使用绕过AttributeError:

class attr_block_meta(type):
    def __new__(meta, cname, bases, dctry):
        def _setattr(self, name, value):
            if not hasattr(self, name):
                raise AttributeError("'" + name + "' not an attibute of " + cname + " object.")
            object.__setattr__(self, name, value)

        cls = type.__new__(meta, cname, bases, dctry)
        cls.__setattr__ = _setattr
        return cls


class ImPoint(object):
    __metaclass__ = attr_block_meta
    def __init__(self, q, z):
        object.__setattr__(self, 'q', q)
        object.__setattr__(self, 'z', z)

point = ImPoint(1, 2)
print point.q, point.z
point.w = 3  # Raises AttributeError
Run Code Online (Sandbox Code Playgroud)