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: 
Python lets you store attributes of any name on virtually any instance (or class, for that matter). It's possible to block this either by writing the class in C, like the built-in types, or by using `__slots__` which allows only certain names.

The reason it works is that most instances store their attributes in a dictionary. Yes, a regular Python dictionary like you'd define with `{}`. The dictionary is stored in an instance attribute called `__dict__`. In fact, some people say "classes are just syntactic sugar for dictionaries." That is, you can do everything you can do with a class with a dictionary; classes just make it easier.

You're used to static languages where you must define all attributes at compile time. In Python, class definitions are *executed*, not compiled; classes are objects just like any other; and adding attributes is as easy as adding an item to a dictionary. This is why Python is considered a *dynamic* language.
To add to [Conchylicultor's answer](https://stackoverflow.com/a/59711821/1719931), Python 3.10 added a new parameter to `dataclass`.

The `slots` parameter will create the `__slots__` attribute in the class, preventing creation of new attributes outside of `__init__`, but allowing assignments to existing attributes.

If `slots=True`, assigning to an attribute that was not defined will throw an `AttributeError`.

Here is an example with `slots` and with `frozen`:

```py
from dataclasses import dataclass

@dataclass
class Data:
    x:float=0
    y:float=0

@dataclass(frozen=True)
class DataFrozen:
    x:float=0
    y:float=0

@dataclass(slots=True)
class DataSlots:
    x:float=0
    y:float=0

p = Data(1,2)
p.x = 5 # ok
p.z = 8 # ok

p = DataFrozen(1,2)
p.x = 5 # FrozenInstanceError
p.z = 8 # FrozenInstanceError

p = DataSlots(1,2)
p.x = 5 # ok
p.z = 8 # AttributeError

```