I think I figured it out. namedtuple generates __slots__ attribute, and if a class is defined with __slots__ attribute, the interpreter doesn't use dictionary to store instance's data, but rather a fixed size memory. This has a side effect of preventing additional fields from being added. Modifying this attribute for an instance, either adding or removing it, has no effect. From what I've read, __slots__ is supposed to hold field names, but namedtuple uses empty tuple, and it works just fine. Go figure. Furthermore, the generated class uses properties for the declared fields, and it lacks setter and deleter, making the objects immutable. Interestingly, I couldn't find any mechanism that would let you bypass those accessor methods - unlike any other dynamically typed language I know. Also, it seems the Python community considers properties to be harmful. I haven't read up the reasons why, but quite a few blog posts with very suggestive titles have popped up while I was googling for all these other things.
Moral of the story: add __slots__ = () to every single Python class you ever write.