11

For example, this code is Python:

a = object()
a.b = 3

throws AttributeError: 'object' object has no attribute 'b'

But, this piece of code:

class c(object): pass
a = c()
a.b = 3

is just fine. Why can I assign property b, when class x does not have that property? How can I make my classes have only properties defined?

pero
  • 113
  • 4
  • `How can I make my classes have only properties defined?` -- It should be pointed out that this is just a lot of extra work for no good reason and perhaps breaking some useful piece of metaprogramming, so it should be avoided unless you have good reasons to do this and know exactly what you're doing. –  May 02 '11 at 20:50

5 Answers5

12

The object type is a built-in class written in C and doesn't let you add attributes to it. It has been expressly coded to prevent it.

The easiest way to get the same behavior in your own classes is to use the __slots__ attribute to define a list of the exact attributes you want to support. Python will reserve space for just those attributes and not allow any others.

class c(object):
    __slots__ = "foo", "bar", "baz"

a = c()

a.foo = 3  # works
a.b   = 3  # AttributeError

Of course, there are some caveats with this approach: you can't pickle such objects, and code that expects every object to have a __dict__ attribute will break. A "more Pythonic" way would be to use a custom __setattr__() as shown by another poster. Of course there are plenty of ways around that, and no way around setting __slots__ (aside from subclassing and adding your attributes to the subclass).

In general, this is not something you should actually want to do in Python. If the user of your class wants to store some extra attributes on instances of the class, there's no reason not to let them, and in fact a lot of reasons why you might want to.

kindall
  • 178,883
  • 35
  • 278
  • 309
  • 2
    This is great, I didn't know about `__slots__` – Mu Mind May 02 '11 at 20:54
  • 2
    Interesting approach! I was not aware of `__slots__`. However I was going over the caveats to this approach and stumbled upon http://stackoverflow.com/questions/472000/python-slots. Some good warning are mentioned in that thread. Especially the lack of a `__dict__` method if we use `__slots__`! – Praveen Gollakota May 02 '11 at 20:58
  • 1
    Yes, those are good caveats. A more Pythonic way would arguably to be to write your own `__setattr__()` to disallow unapproved attributes. Of course, using `__slots__` is going to save memory and be faster. – kindall May 02 '11 at 21:02
  • Edited to call out the pitfalls. – kindall May 02 '11 at 21:05
3

You can override the behavior of the __setattr__ magic method like so.

class C(object):
    def __setattr__(self, name, value):
        allowed_attrs = ('a', 'b', 'c')
        if name not in allowed_attrs:
            # raise exception
            # or do something else
            pass
        self.__dict__[name] = value

Of course, this will only prevent you from setting attributes like a.b (the dot form). You can still set the attributes using a.__dict__[b] = value. In that case, you should override the __dict__ method too.

Praveen Gollakota
  • 37,112
  • 11
  • 62
  • 61
2

Python generally allows you to set any attribute on any object. This is a special case where the object class acts differently. There are also some modules implemented in C that act similarly.

If you want your object to behave like this, you can define a __setattr__(self, name, value) method that explicitly does a raise AttributeError() if you try to set a member that's not on the "approved list" (see http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/389916)

Mu Mind
  • 10,935
  • 4
  • 38
  • 69
1

Creating an object instance has no features. Therefore setting attributes on an instance of a the base object type is expressly disabled. You must subclass it to be able to create attributes.

Hint: If you want a simple object to use as something on which to store properties, you can do so by creating an anonymous function with lambda. Functions, being objects, are able to store attributes as well, so this is perfectly legit:

>>> a = lambda: None
>>> a.b = 3
>>> a.b 
3
jathanism
  • 33,067
  • 9
  • 68
  • 86
-1

This happens because when you say a.b = 3, it creates a variable in a that represents b. For example,

class a: pass
print a.b

returns AttributeError: class a has no attribute b

However this code,

class a: pass
a.b = 3
print a.b

returns 3 as it sets the value of b in a, to 3.

Matt Habel
  • 1,493
  • 2
  • 14
  • 35