Python Cookbook-6.2 定义常量
任务
你需要定义一些模块级别的变量(比如命名的常量),而且客户代码无法将其重新绑定。
解决方案
你可以把任何对象当做模块一样安装。将下列代码存为一个模块const.py,并放入你的Python的sys.path 指定的目录中:
class _const(object):
class ConstError(TypeError):pass
def __setattr__(self,name,value):
if name in self.__dict__
raise self.ConstError,"Can't rebind const(%s)" %name
self.__dict__[name] = value
def __delattr__self,name):
if name in self.__dict__:
raise self.ConstError,"Can't unbind const(%s) %name
raise NameError,name
import sys
sys.modules[__name__] = const()
现在,任何客户代码都可以导入 const,并将const 模块的一个属性绑定一次,但仅能绑定一次,如下:
const.magic = 23
一旦某属性已经被绑定,程序无法将其重新绑定或者解除绑定:
Const.magic = 88#抛出const.ConstError
del const.magic#抛出const.ConstError
讨论
在 Python 中,变量可以被随意绑定,而模块不同于类,你无法定义一个特殊的像__setattr__一样的方法来阻止对它的重新绑定。一个简单的解决办法是像安装模块一样安装对象。
Python 并没有对 sys.modules 的条目做类型检査以确保它们是模块对象。因此,可以在那里安装一个对象,并利用其特殊的属性存取的方法(比如在__getattr__等方法中阻止对属性的处理或者重新绑定),而且客户代码仍然可以通过import somename 来访问这个对象。还可以在更具有 Python 风格的单例模式用法(见6.16节)中看到这种技巧本节解决方案可以确保:当一个模块级别的名字被首次绑定到了一个对象,以后它会始终恒定不变地绑定到同一个对象。本节并没有处理某个对象的不可变性,那是另一个不同的主题。修改一个对象和重新绑定一个名字是完全不同的概念,这些概念在 4.1节中有详细的解释。数字、字符串和元组都是不可改变的:如果你将const中的一个名字绑定到这样的一个对象,那么不仅该名字会始终绑定到此对象,而且对象的内容也会永远保持不变,因为这个对象具有不可变性。但其他对象比如列表和字典则是可变的:如果绑定 const 中的一个名字到一个列表对象,虽然这个名字会始终保持绑定到该列表,但列表的内容却可能发生变化(比如其中的元素可能被重新绑定或者解除绑定我们通过 append 方法也可以给它增加更多的元素,等等)。
关于怎样给可改变的对象制作一个“只读”的封装层的内容,请参考6.5节。可以选择用类_const的__setattr__方法来完成这个封装。假设将6.5节中的代码存为 ro.py模块,并放入你的 Python的sys.path目录。那么,需要在 const.py 模块的开头增加一句:
import ro
并修改类_const的__setattr__方法中的赋值语句 self,__dict__[name] = value,将其改为:
self.__dict__[name] = ro.Readonly(value)
现在,当你将 const 中的属性设置为某值时,实际上只是把只读的封装绑定到了那个值我们通过这个值(对象)的其他的引用,调用相关的修改函数或方法仍然能够修改它当然通过“伪模块”const 的属性无法实现修改。如果你想避免这种“意外地通过其他的引用修改”的情况,需要使用一个拷贝,如同4.1节所解释的那样,这样才能保证没有任何其他引用指向那个值。为了达到这个目的,必须在const.py 模块开始的位置编写这样的代码:
import ro,copy
#并且将类_const中的__setattr_方法改成:
self.__dict__[name] = ro.Readonly(copy.copy(value))
如果你够偏执,你可能还会想用copy.deepcopy 替代最后一段代码中的copy.copy。但是过度谨慎会消耗更多的内存,并损失一些性能。必须根据自己特定的应用需求来仔细衡量这样的举措是否真的有必要。无论你最终的决定是什么,Python已经向我们证明了,它提供了足够的工具和机制来实现我们所需要的不可变性。
本节展示的_const 类,从某种意义来说,它可以被看做是6.3节中展示的 NoNewAttrs类的“补充”。_const 类确保已经被绑定的属性无法被重新绑定,但允许你随意绑定新属性;而 NoNewAttrs 则相反,可以随意地重新绑定那些已经被绑定的属性,但是却禁止绑定任何新的属性。