Other than dataclasses you don't see typing used much for instance variables. This is likely because instance variables are really just entries in a dictionary. Since instance variables are really just dictionary entries, you can add instance variables at any time, not just in the __init__ method. Even dataclass objects allow adding attributes. Using Yoriz' example:
from dataclasses import dataclass
@dataclass
class Node:
value: float
next: "Node" = None
prev: "Node" = None
x = Node(5)
x.string = "Hello"
print(x, x.string)
Output:
Node(value=5, next=None, prev=None) Hello
"string" is an attribute of x. x might be the only Node in history to have a "string" attribute, making x not only different from other Note objects by having a unique ID and different instance variable values, but also by having an additional instance variable.
If you want to restrict class instances to only having variables that you define, you can also use Pydantic. Defining a class using pydantic looks very, very similar to defining a dataclass.
from pydantic import BaseModel
class Node(BaseModel):
value: float
prev: "Node" = None
next: "Node" = None
x = Node(value=5)
x.string = "Hello"
Error:
ValueError: "Node" object has no field "string"
Instead of adding a "string" attribute to x we get a ValueError. BaseModel overrides __setattr__() to raise an exception instead of adding the "string":"Hello" to the object dictionary (x.__dict__).
Another way to get very similar behavior is using __slots__.
class Node():
__slots__ = ("value", "prev", "next")
value: float
prev: "Node"
next: "Node"
def __init__(self, value:float, prev:"Node" = None, next:"Node" = None):
self.value = value
self.prev = prev
self.next = next
x = Node(value=5)
x.string = "Hello"
Error:
AttributeError: 'Node' object has no attribute 'string'
Here we get an AttributeError, which I think is a better choice than a ValueError. The error is raised not because something overrode the normal __setattr__() behavior to prevent adding items to the object dictionary, but because classes that define __slots__ don't have an object dictionary at all. When a class uses __slots__, all instances of the class have the same attributes. The attributes can have different values, but you cannot add any new instance variables.