Welcome, guest | Sign In | My Account | Store | Cart

This recipe is here for a couple of reasons: 1) discourage a common misuse of __slots__; 2) show how to restrict Python dynamism.

Python, 17 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# requires Python 2.2+

def frozen(set):
    "Raise an error when trying to set an undeclared name."
    def set_attr(self,name,value):
        if hasattr(self,name):
            set(self,name,value) 
        else:
            raise AttributeError("You cannot add attributes to %s" % self)
    return set_attr

class Frozen(object):
    """Subclasses of Frozen are frozen, i.e. it is impossibile to add
     new attributes to them and their instances."""
    __setattr__=frozen(object.__setattr__)
    class __metaclass__(type):
        __setattr__=frozen(type.__setattr__)

__slots__ are a Python 2.2 feature intended as a memory optimization: however, judging from recent posts in c.l.py, lots of people have misunderstood its aim, and think __slots__ is used to introduce declarations in Python. The reason why they think so is that it is impossible to add undeclared run-time attributes to instances of classes with __slots__. This is a limitation of __slots__, not a feature!

Nevertheless there are people who want to restrict Python dynamism, for various reasons. The right way to do it is not via __slots__, but via __setattr__. Here I show a simple recipe - which maybe expanded and customized - to restrict the dynamism of Python classes.

Notice that the recipe inhibits not only the addition of runtime attributes to objects, but even to classes.

Here is an example of usage:

<pre> class Person(Frozen): firstname="" lastname="" def __init__(self,firstname,lastname): self.firstname=firstname self.lastname=lastname

me=Person("Michele","Simionato")

</pre> Using this "feature" one is forced to declare the attributes of a class explicitly since setting an undeclared attribute raises an error:

>>> Person.add_an_attribute="something" # => Attribute Error
>>> me.add_an_attribute="something" # => Attribute Error

Also, the normal Python idiom "self.somename=something" raises an error if "somename" is not explicitely declared in the class. In other words, subclasses of "Frozen" behaves more similarly to Java/C++ classes, so this limitation may be useful in the coding of prototypes to be converted in static languages.

1 comment

Michael Loritsch 19 years, 5 months ago  # | flag

Getting rid of the instance and class level variable dependency... This is a very useful recipe, but it has one large wart...

In order to define an instance variables, one must also define a class (static) variable of the same name, and vice versa.

A more pythonic solution would be adjust the frozen function to only allow attributes to be set when either:

1) They already exist

2) They are being set from within an __init__ method of either the Frozen class, or a derived class.

This way, we are providing the python equivalent to C++ and Java's declarations, but we are doing it in a pythonic way, in the __init__ method of our object. After all, python objects do not have declarations, nor should they...

In order to do this, we need to alter the freeze function as follows:

def frozen(set):
    """Raise an error when trying to set an undeclared name, or when calling
       from a method other than Frozen.__init__ or the __init__ method of
       a class derived from Frozen"""
    def set_attr(self,name,value):
        import sys
        if hasattr(self,name):                                  #If attribute already exists, simply set it
            set(self,name,value)
            return
        elif sys._getframe(1).f_code.co_name is '__init__':     #Allow __setattr__ calls in __init__ calls of proper object types
            for k,v in sys._getframe(1).f_locals.items():
                if k=="self" and isinstance(v, self.__class__):
                    set(self,name,value)
                    return
        raise AttributeError("You cannot add attributes to %s" % self)
    return set_attr

Here are a few examples of usage with the modified code

class Person(Frozen):
    def __init__(self,firstname,lastname):
        self.firstname=firstname
        self.lastname=lastname

class AgedPerson(Person):
    def __init__(self, firstname, lastname, age):
        Person.__init__(self, firstname, lastname)
        self.age = age
        self.firstname = " ".join(("Aged", firstname))

me=Person("Michael", "Loritsch")
agedMe = AgedPerson("Michael", "Loritsch", 31)

Warmest Regards,

Michael Loritsch

Created by Michele Simionato on Thu, 20 Nov 2003 (PSF)
Python recipes (4591)
Michele Simionato's recipes (12)

Required Modules

  • (none specified)

Other Information and Tasks