目前我覆盖了班级' __setattr__
()在课程结束时#39;阻止新属性创建的__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)
有没有办法避免手动覆盖__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
是否有更通用的方法来执行此操作,以便不需要对子类属性的先验知识?
基本上,如何避免行dctry.update({'x': 0, 'y': 0})
并使其工作,而不管类属性的名称是什么?
P.S。 - FWIW我已经评估了__slots__
和namedtuple选项,发现它们缺乏我的需求。请不要将注意力集中在我用来说明问题的削减点()示例上;实际的用例涉及更复杂的类。
答案 0 :(得分:10)
不要重新发明轮子。
实现这一目标的两种简单方法(不直接使用元类)使用:
namedtuple
Š__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
答案 1 :(得分:6)
您不需要元类来解决此类问题。
如果你想预先创建数据然后让它变成不可变的话,我肯定会使用namedtuple作为shx2建议。
否则,只需在类上定义一组允许的字段,然后__setattr__
检查您尝试设置的名称是否在允许的字段集合中。您无需在__setattr__
中部分地更改__init__
的实施方式 - 它将在__init__
期间工作,这与以后的工作方式相同。如果您不想阻止在给定类上更改/更改它们,请使用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 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 ,并且它会导致需要其他属性的子类的一些复杂化(子类将具有确保_allowed_attrs
字段具有适合它的内容。)
答案 2 :(得分: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
有关“换行”的更多详情,请参阅this。
您可能需要更多地使用它来使其更优雅,但一般的想法是仅在调用 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
答案 3 :(得分:1)
我有同样的需求(对于开发快速入侵API)。我没有使用元类,只是继承:
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()
如果我需要子类Base并添加额外的属性,我使用unlock:
class Child(Base):
def __init__(self):
Base.__init__(self)
self._unlock()
self.c = 2
self._lock()
如果Base是抽象的,您可以跳过锁定并锁定孩子。 我有一些单元测试,检查每个公共类在init之后被锁定,以便在忘记锁定时抓住我。