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
不要重新发明轮子.
实现该目的的两种简单方法(不直接使用元类)使用:
例如,使用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)
您不需要元类来解决此类问题.
如果你想一次创建的数据前面,然后把它是不可变的,我肯定会用一个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
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
字段具有适合它的内容) ).
这对你的案子有意义吗?
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)