如何编写元类以防止在 __init__() 之后创建新属性?
How to write metaclass which would prevent creating new attributes after __init__()?
目前我在 class' __init__
() 方法的末尾重写 class' __setattr__
() 以防止创建新属性 -
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)
有没有办法避免手动覆盖 __setattr__
() 并在 metaclasses 的帮助下自动执行此操作?
我最接近的是-
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
是否有更通用的方法可以不需要子class 属性的先验知识?
基本上,如何避免行 dctry.update({'x': 0, 'y': 0})
并使这项工作不管 class 属性的名称是什么?
P.S。 - FWIW 我已经评估了 __slots__
和 namedtuple 选项,发现它们不符合我的需要。请不要将注意力集中在我用来说明问题的精简 Points() 示例上;实际用例涉及复杂得多 class.
不要重新发明轮子。
实现该目标的两种简单方法(不直接使用元类)使用:
namedtuple
s
__slots__
例如,使用 namedtuple
(基于文档中的示例):
Point = namedtuple('Point', ['x', 'y'])
p = Point(11, 22)
p.z = 33 # ERROR
例如,使用__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
你不需要元classes 来解决这类问题。
如果您想预先创建一次数据然后使其不可变,我肯定会按照 shx2 的建议使用 namedtuple。
否则,只需在 class 上定义一组允许的字段,然后让 __setattr__
检查您尝试设置的名称是否在允许的字段集合中。您不需要在 __init__
中途更改 __setattr__
的实现——它在 __init__
期间的工作方式与以后的工作方式相同。如果您想在给定的 class.
上阻止 mutating/changing 它们,请使用 tuple
或 frozenset
作为允许字段的数据结构
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
这可以很容易地分解成一个基础 -class 以供重复使用:
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
我不认为 pythonic 以这种方式锁定对象的允许属性,这会给 sub[=33= 带来一些复杂性]es 需要额外的属性(subclass 必须确保 _allowed_attrs
字段具有适合它的内容)。
这对你的情况有意义吗?
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
See this for more details on 'wraps'.
您可能需要 fiddle 多一点,让它更优雅,但一般的想法是仅在 init[=21= 之后重写 __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
我也有同样的需求(为了快速开发 API)。我不为此使用 metaclasses,只是继承:
class LockedObject(object):
def __setattr__(self, name, value):
if name == "_locked":
object.__setattr__(self, name, value)
return
if hasattr(self, "_locked"):
if not self._locked or hasattr(self, name):
object.__setattr__(self, name, value)
else:
raise NameError("Not allowed to create new attribute {} in locked object".format(name))
else: # never called _lock(), so go on
object.__setattr__(self, name, value)
def _lock(self):
self._locked = True
def _unlock(self):
self._locked = False
然后:
class Base(LockedObject):
def __init__(self):
self.a = 0
self.b = 1
self._lock()
如果我需要子class 基础并添加额外的属性我使用解锁:
class Child(Base):
def __init__(self):
Base.__init__(self)
self._unlock()
self.c = 2
self._lock()
如果 Base 是抽象的,您可以跳过它的锁定而只锁定子级。
然后我进行了一些单元测试,检查每个 public class 在初始化后是否被锁定,如果我忘记锁定就可以抓住我。
目前我在 class' __init__
() 方法的末尾重写 class' __setattr__
() 以防止创建新属性 -
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)
有没有办法避免手动覆盖 __setattr__
() 并在 metaclasses 的帮助下自动执行此操作?
我最接近的是-
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
是否有更通用的方法可以不需要子class 属性的先验知识?
基本上,如何避免行 dctry.update({'x': 0, 'y': 0})
并使这项工作不管 class 属性的名称是什么?
P.S。 - FWIW 我已经评估了 __slots__
和 namedtuple 选项,发现它们不符合我的需要。请不要将注意力集中在我用来说明问题的精简 Points() 示例上;实际用例涉及复杂得多 class.
不要重新发明轮子。
实现该目标的两种简单方法(不直接使用元类)使用:
namedtuple
s__slots__
例如,使用 namedtuple
(基于文档中的示例):
Point = namedtuple('Point', ['x', 'y'])
p = Point(11, 22)
p.z = 33 # ERROR
例如,使用__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
你不需要元classes 来解决这类问题。
如果您想预先创建一次数据然后使其不可变,我肯定会按照 shx2 的建议使用 namedtuple。
否则,只需在 class 上定义一组允许的字段,然后让 __setattr__
检查您尝试设置的名称是否在允许的字段集合中。您不需要在 __init__
中途更改 __setattr__
的实现——它在 __init__
期间的工作方式与以后的工作方式相同。如果您想在给定的 class.
tuple
或 frozenset
作为允许字段的数据结构
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
这可以很容易地分解成一个基础 -class 以供重复使用:
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
我不认为 pythonic 以这种方式锁定对象的允许属性,这会给 sub[=33= 带来一些复杂性]es 需要额外的属性(subclass 必须确保 _allowed_attrs
字段具有适合它的内容)。
这对你的情况有意义吗?
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
See this for more details on 'wraps'.
您可能需要 fiddle 多一点,让它更优雅,但一般的想法是仅在 init[=21= 之后重写 __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
我也有同样的需求(为了快速开发 API)。我不为此使用 metaclasses,只是继承:
class LockedObject(object):
def __setattr__(self, name, value):
if name == "_locked":
object.__setattr__(self, name, value)
return
if hasattr(self, "_locked"):
if not self._locked or hasattr(self, name):
object.__setattr__(self, name, value)
else:
raise NameError("Not allowed to create new attribute {} in locked object".format(name))
else: # never called _lock(), so go on
object.__setattr__(self, name, value)
def _lock(self):
self._locked = True
def _unlock(self):
self._locked = False
然后:
class Base(LockedObject):
def __init__(self):
self.a = 0
self.b = 1
self._lock()
如果我需要子class 基础并添加额外的属性我使用解锁:
class Child(Base):
def __init__(self):
Base.__init__(self)
self._unlock()
self.c = 2
self._lock()
如果 Base 是抽象的,您可以跳过它的锁定而只锁定子级。 然后我进行了一些单元测试,检查每个 public class 在初始化后是否被锁定,如果我忘记锁定就可以抓住我。