Python Cookbook-6.3 限制属性的设置
任务
通常情况下,Python 允许你随意给类和类实例增加属性。但对于某些特定的类,你却希望这种自由受到限制。
解决方案
特殊方法__setattr__会解读对属性的设置操作,它让你有机会限制新属性的添加。种优雅的实现方法是写一个类和一个简单的自定义元类,再加上一个封装函数,像下面这样:
def no_new_attributes(wrapped_setattr):'''试图添加新属性时,报错但允许已经存在的属性被随意设置'''def __setattr__(self,name,value):if hasattr(self,name):#非新属性,允许wrapped_setattr(self,name,value)#新属性,禁止else:raise AttributeError("can't add attribute %r to %s" %(name,self)return setattr
class NoNewAttrs(object):'''NoNewAttrs的子类会拒绝新属性的添加但允许已存在的属性被赋予新值'''#向此类的实例添加新属性的操作被屏蔽__setattr__ = no_new_attributes(object.__setattr__)class __metaclass__(type):'''一个简单的自定义元类,禁止向类添加新属性'''__setattr__ = no_new_attributes(type.__setattr__)
讨论
由于某些原因,有时你会想要限制 Python 的动态能力。比如,对于某个特定的类或其实例,有时你可能会意外地给它设置了新的属性,而你不希望这种情况发生。本节的解决方案介绍了怎样实现这样的限制。其要点是,不要为这个目的使用__siots__:__slots__ 应该被用于其他的任务(比如,你的类有一些固定的属性并有数量很多的实例,利用__slots__可以避免每个实例都拥有一个字典,从而节省很多内存)。__slots__应该被用来做它适合做的事,如果你试图用它来完成本节的任务,会遇到很多限制(见6.7节中一个使用__slots __来节省内存的例子)。
需要注意的是,本节解决方案给出的方法阻止了在运行时添加属性,而且不仅对类实例有效,对类本身也适用,这应该归功于那个简单的自定义元类。当你想要限制一些意外添加属性的行为时,你通常会希望类和它的每个实例都受到限制。另一方面,类和类实例已经存在的属性则可以被随意地设置新值,下面给出一个使用这个类的例子:
class Person(NoNewAttrs):firstname = ''lastname = ''def __init__(self,firstname,lastname):self.firstname = firstnameself.lastname = lastnamedef __repr__(self):return 'Person'(%r,%r) %(self.firstname,self.lastname)
me = Person("Michere","Simionato")
print me
#输出:Person('Michere','Simionate')
#firstname的值是错的,能修正吗?当然没问题!
me.firstname = "Michele"
print me
#输出:Person('Michele',isimionato')
从 NoNewAttrs 继承将迫使你只能在类的内部“声明”那些允许被设置的属性。如果你想在进一步使用中设置一个“未声明”的属性,只会得到一个AttributeError:
try:Person.address = ''
except AttributeError,err:print 'raised %r as expected' %err
try:me.address = ''
except AttributeError,err:print 'raised %r as expected' %err
从某种意义上讲,NoNewAtr的子类以及它们的实例其实很像Java或 C++的类与实例,而不像普通的 Python 类和实例。因此,当你在用Python 构建某种原型而且最后会用其他动态性逊于Python的语言来重新编写代码的时候,本节提供的方法会很适合你的需求。