Recipe6.2.Defining Constants


Recipe 6.2. Defining Constants

Credit: Alex Martelli

Problem

You need to define module-level variables (i.e., named constants) that client code cannot accidentally rebind.

Solution

You can install any object as if it were a module. Save the following code as module const.py on some directory on your 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( )

Now, any client code can import const, then bind an attribute on the const module just once, as follows:

const.magic = 23

Once the attribute is bound, the program cannot accidentally rebind or unbind it:

const.magic = 88      # raises const.ConstError del const.magic       # raises const.ConstError

Discussion

In Python, variables can be rebound at will, and modules, differently from classes, don't let you define special methods such as _ _setattr_ _ to stop rebinding. An easy solution is to install an instance as if it were a module.

Python performs no type-checks to force entries in sys.modules to actually be module objects. Therefore, you can install any object there and take advantage of attribute-access special methods (e.g., to prevent rebinding, to synthesize attributes on the fly in _ _getattr_ _, etc.), while still allowing client code to access the object with import somename. You may even see it as a more Pythonic Singleton-style idiom (but see Recipe 6.16).

This recipe ensures that a module-level name remains constantly bound to the same object once it has first been bound to it. This recipe does not deal with a certain object's immutability, which is quite a different issue. Altering an object and rebinding a name are different concepts, as explained in Recipe 4.1. Numbers, strings, and tuples are immutable: if you bind a name in const to such an object, not only will the name always be bound to that object, but the object's contents also will always be the same since the object is immutable. However, other objects, such as lists and dictionaries, are mutable: if you bind a name in const to, say, a list object, the name will always remain bound to that list object, but the contents of the list may change (e.g., items in it may be rebound or unbound, more items can be added with the object's append method, etc.).

To make "read-only" wrappers around mutable objects, see Recipe 6.5. You might choose to have class _const's _ _setattr_ _ method perform such wrapping implicitly. Say you have saved the code from Recipe 6.5 as module ro.py somewhere along your Python sys.path. Then, you need to add, at the start of module const.py:

import ro

and change the assignment self._ _dict_ _[name] = value, used in class _const's _ _setattr_ _ method to:

    self._ _dict_ _[name] = ro.Readonly(value)

Now, when you set an attribute in const to some value, what gets bound there is a read-only wrapper to that value. The underlying value might still get changed by calling mutators on some other reference to that same value (object), but it cannot be accidentally changed through the attribute of "pseudo-module" const. If you want to avoid such "accidental changes through other references", you need to take a copy, as explained in Recipe 4.1, so that there exist no other references to the value held by the read-only wrapper. Ensure that at the start of module const.py you have:

import ro, copy

and change the assignment in class _const's _ _setattr_ _ method to:

    self._ _dict_ _[name] = ro.Readonly(copy.copy(value))

If you're sufficiently paranoid, you might even use copy.deepcopy rather than plain copy.copy in this latest snippet. However, you may end up paying substantial amounts of memory, as well as losing some performance, by these kinds of excessive precautions. You should evaluate carefully whether so much prudence is really necessary for your specific application. Whatever you end up deciding about this issue, Python offers all the tools you need to implement exactly the amount of constantness you require.

The _const class presented in this recipe can be seen, in a sense, as the "complement" of the NoNewAttrs class, which is presented next in Recipe 6.3. This one ensures that already bound attributes can never be rebound but lets you freely bind new attributes; the other one, conversely, lets you freely rebind attributes that are already bound but blocks the binding of any new attribute.

See Also

Recipe 6.5; Recipe 6.13; Recipe 4.1; Library Reference and Python in a Nutshell docs on module objects, the import statement, and the modules attribute of the sys built-in module.



Python Cookbook
Python Cookbook
ISBN: 0596007973
EAN: 2147483647
Year: 2004
Pages: 420

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net