Question: I am studying python, and although I think I get the whole concept and notion of Python, today I stumbled upon a piece of code that I did not fully understand:

Say I have a class that is supposed to define Circles but lacks a body:

```
class Circle():
    pass

```

Since I have not defined any attributes, how can I do this:

```
my_circle = Circle()
my_circle.radius = 12

```

The weird part is that Python accepts the above statement. I don't understand why Python doesn't raise an `undefined name error`. I do understand that via *dynamic typing* I just bind variables to objects whenever I want, but shouldn't an attribute `radius` exist in the `Circle` class to allow me to do this?

**EDIT**: Lots of wonderful information in your answers! **Thank you everyone for all those fantastic answers!** It's a pity I only get to mark one as an answer.

Answer: 
Just to clarify some misunderstandings in the discussions here. This code:

```
class Foo(object):
    def __init__(self, bar):
        self.bar = bar

foo = Foo(5)

```

And this code:

```
class Foo(object):
    pass

foo = Foo()
foo.bar = 5

```

is *exactly equivalent*. There really is no difference. It does exactly the same thing. This difference is that in the first case it's encapsulated and it's clear that the bar attribute is a normal part of Foo-type objects. In the second case it is not clear that this is so.

In the first case you can not create a Foo object that doesn't have the bar attribute (well, you probably can, but not easily), in the second case the Foo objects will not have a bar attribute unless you set it.

So although the code is programatically equivalent, it's used in different cases.
How to prevent new attributes creation ?
========================================

Using class
-----------

To control the creation of new attributes, you can overwrite the `__setattr__` method. It will be called every time `my_obj.x = 123` is called.

See the [documentation](https://docs.python.org/3/reference/datamodel.html#object.__setattr__):

```py
class A:
  def __init__(self):
    # Call object.__setattr__ to bypass the attribute checking
    super().__setattr__('x', 123)

  def __setattr__(self, name, value):
    # Cannot create new attributes
    if not hasattr(self, name):
      raise AttributeError('Cannot set new attributes')
    # Can update existing attributes
    super().__setattr__(name, value)

a = A()
a.x = 123  # Allowed
a.y = 456  # raise AttributeError

```

Note that users can still bypass the checking if they call directly `object.__setattr__(a, 'attr_name', attr_value)`.

Using dataclass
---------------

With `dataclasses`, you can forbid the creation of new attributes with `frozen=True`. It will also prevent existing attributes to be updated.

```
@dataclasses.dataclass(frozen=True)
class A:
  x: int

a = A(x=123)
a.y = 123  # Raise FrozenInstanceError
a.x = 123  # Raise FrozenInstanceError

```

Note: `dataclasses.FrozenInstanceError` is a subclass of AttributeError