类似于sealed class的作用,并且有助于减少内存使用(
__slots__的用途?)是
__slots__
属性,该属性可以防止monkey patching一个class。因为当metaclass
__new__
被调用时,把
__slots__
放入class已经太晚了,我们必须在第一个可能的时间点即
__prepare__
期间将其放入namespace中。此外,这会更早地引发TypeError异常。使用mcs进行isinstance比较可以消除在元类本身中硬编码元类名称的必要性。缺点是所有未slotted的属性都是只读的。因此,如果我们想在初始化或以后设置特定的属性,则必须明确指定它们为slotted。这可以通过使用将slots作为参数的动态元类来实现。
def Final(slots=[]):
if "__dict__" in slots:
raise ValueError("Having __dict__ in __slots__ breaks the purpose")
class _Final(type):
@classmethod
def __prepare__(mcs, name, bases, **kwargs):
for b in bases:
if isinstance(b, mcs):
msg = "type '{0}' is not an acceptable base type"
raise TypeError(msg.format(b.__name__))
namespace = {"__slots__":slots}
return namespace
return _Final
class Foo(metaclass=Final(slots=["_z"])):
y = 1
def __init__(self, z=1):
self.z = 1
@property
def z(self):
return self._z
@z.setter
def z(self, val:int):
if not isinstance(val, int):
raise TypeError("Value must be an integer")
else:
self._z = val
def foo(self):
print("I am sealed against monkey patching")
试图覆盖foo.foo将会抛出AttributeError: 'Foo' object attribute 'foo' is read-only
,而尝试添加foo.x将会抛出AttributeError: 'Foo' object has no attribute 'x'
。当继承时,__slots__
的限制力量会被打破,但由于Foo具有元类Final,因此无法从中继承。如果dict在slots中,则也会被打破,因此我们会抛出ValueError。总之,为slotted属性定义setter和getter可以限制用户如何覆盖它们。
foo = Foo()
foo.foo()
print(foo.y)
foo.z = 2
foo.foo = lambda:print("Guerilla patching attempt")
foo.z = foo.foo
foo.x = 1
class Bar(Foo):
pass
在这方面,Foo 无法被继承或扩展。缺点是所有属性都必须显式地插入槽中,或者仅限于只读类变量。
seal
ing类可以鼓励这样做...虽然这可能不是它预期的使用方式 ;) - Chris Pfohl