Recipe6.3.Restricting Attribute Setting


Recipe 6.3. Restricting Attribute Setting

Credit: Michele Simionato

Problem

Python normally lets you freely add attributes to classes and their instances. However, you want to restrict that freedom for some class.

Solution

Special method _ _setattr_ _ intercepts every setting of an attribute, so it lets you inhibit the addition of new attributes that were not already present. One elegant way to implement this idea is to code a class, a simple custom metaclass, and a wrapper function, all cooperating for the purpose, as follows:

def no_new_attributes(wrapped_setattr):     """ raise an error on attempts to add a new attribute, while         allowing existing attributes to be set to new values.     """     def _ _setattr_ _(self, name, value):         if hasattr(self, name):    # not a new attribute, allow setting             wrapped_setattr(self, name, value)         else:                      # a new attribute, forbid adding it             raise AttributeError("can't add attribute %r to %s" % (name, self))     return _ _setattr_ _ class NoNewAttrs(object):     """ subclasses of NoNewAttrs inhibit addition of new attributes, while         allowing existing attributed to be set to new values.     """     # block the addition new attributes to instances of this class     _ _setattr_ _ = no_new_attributes(object._ _setattr_ _)     class _ _metaclass_ _(type):         " simple custom metaclass to block adding new attributes to this class "         _ _setattr_ _ = no_new_attributes(type._ _setattr_ _)

Discussion

For various reasons, you sometimes want to restrict Python's dynamism. In particular, you may want to get an exception when a new attribute is accidentally set on a certain class or one of its instances. This recipe shows how to go about implementing such a restriction. The key point of the recipe is, don't use _ _slots_ _ for this purpose: _ _slots_ _ is intended for a completely different task (i.e., saving memory by avoiding each instance having a dictionary, as it normally would, when you need to have vast numbers of instances of a class with just a few fixed attributes). _ _slots_ _ performs its intended task well but has various limitations when you try to stretch it to perform, instead, the task this recipe covers. (See Recipe 6.18 for an example of the appropriate use of _ _slots_ _ to save memory.)

Notice that this recipe inhibits the addition of runtime attributes, not only to class instances, but also to the class itself, thanks to the simple custom metaclass it defines. When you want to inhibit accidental addition of attributes, you usually want to inhibit it on the class as well as on each individual instance. On the other hand, existing attributes on both the class and its instances may be freely set to new values.

Here is an example of how you could use this recipe:

class Person(NoNewAttrs):     firstname = ''     lastname = ''     def _ _init_ _(self, firstname, lastname):         self.firstname = firstname         self.lastname = lastname     def _ _repr_ _(self):         return 'Person(%r, %r)' % (self.firstname, self.lastname) me = Person("Michere", "Simionato") print me # emits: Person('Michere', 'Simionato') # oops, wrong value for firstname, can we fix it?  Sure, no problem! me.firstname = "Michele" print me # emits: Person('Michele', 'Simionato')

The point of inheriting from NoNewAttrs is forcing yourself to "declare" all allowed attributes by setting them at class level in the body of the class itself. Any further attempt to set a new, "undeclared" attribute raises an 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

In some ways, therefore, subclasses of NoNewAttr and their instances behave more like Java or C++ classes and instances, rather than normal Python ones. Thus, one use case for this recipe is when you're coding in Python a prototype that you already know will eventually have to be recoded in a less dynamic language.

See Also

Library Reference and Python in a Nutshell documentation on the special method _ _setattr_ _ and on custom metaclasses; Recipe 6.18 for an example of an appropriate use of _ _slots_ _ to save memory; Recipe 6.2 for a class that is the complement of this one.



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