15

I just spent too long on a bug like the following:

>>> class Odp():
    def __init__(self):
        self.foo = "bar"


>>> o = Odp()
>>> o.raw_foo = 3 # oops - meant o.foo

I have a class with an attribute. I was trying to set it, and wondering why it had no effect. Then, I went back to the original class definition, and saw that the attribute was named something slightly different. Thus, I was creating/setting a new attribute instead of the one meant to.

First off, isn't this exactly the type of error that statically-typed languages are supposed to prevent? In this case, what is the advantage of dynamic typing?

Secondly, is there a way I could have forbidden this when defining Odp, and thus saved myself the trouble?

Nick Heiner
  • 119,074
  • 188
  • 476
  • 699
  • 1
    Possible duplicate: http://stackoverflow.com/questions/3079306/how-to-protect-againt-typos-when-setting-value-for-class-members – detly Jun 29 '10 at 03:06

1 Answers1

22

You can implement a __setattr__ method for the purpose -- that's much more robust than the __slots__ which is often misused for the purpose (for example, __slots__ is automatically "lost" when the class is inherited from, while __setattr__ survives unless explicitly overridden).

def __setattr__(self, name, value):
  if hasattr(self, name):
    object.__setattr__(self, name, value)
  else:
    raise TypeError('Cannot set name %r on object of type %s' % (
                    name, self.__class__.__name__))

You'll have to make sure the hasattr succeeds for the names you do want to be able to set, for example by setting the attributes at a class level or by using object.__setattr__ in your __init__ method rather than direct attribute assignment. (To forbid setting attributes on a class rather than its instances you'll have to define a custom metaclass with a similar special method).

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • why did you choose `TypeError`? Maybe wrongfully, I had `AttributeError` in mind. – n611x007 Jan 21 '15 at 10:04
  • 2
    "does not support XX assignment" messages (e.g for items) use `TypeError`, more confusingly worded "has no attribute XX" ones use `AttributeError`, so I guess either is OK (though neither quite perfect). – Alex Martelli Jan 21 '15 at 15:01