Python Forum
Variable Types in a class - Printable Version

+- Python Forum (https://python-forum.io)
+-- Forum: Python Coding (https://python-forum.io/forum-7.html)
+--- Forum: General Coding Help (https://python-forum.io/forum-8.html)
+--- Thread: Variable Types in a class (/thread-38386.html)



Variable Types in a class - nafshar - Oct-06-2022

When looking at a class, how do we know what the types of variables are? as an example:
class Node:
    def __init__(self, value, next_node=None, prev_node=None):
        self.value = value
        self.next = next_node
        self.prev = prev_node
How do we know that self.next is a "Node" type?

Thanks


RE: Variable Types in a class - XavierPlatinum - Oct-06-2022

Python has a type() function for just that.

print(type(your_variable))



RE: Variable Types in a class - nafshar - Oct-06-2022

(Oct-06-2022, 07:31 PM)XavierPlatinum Wrote: Python has a type() function for just that.

print(type(your_variable))

I realize you can get the type programmatically, but when simply reviewing the code to understand it, how can you tell what the type is?


RE: Variable Types in a class - deanhystad - Oct-06-2022

Use type annotations. This example is tricky since Node has not been defined. The convention for this case is to use a string.
class Node:
    def __init__(self, value:float, prev:'Node' = None, next:'Node' = None):
        self.value = value
        self.prev = prev
        self.next = next
You can also use typing_extensions.Self
from typing_extensions import Self

class Node:
    def __init__(self, value:float, prev:Self = None, next:Self = None):
        self.value = value
        self.prev = prev
        self.next = next



RE: Variable Types in a class - ndc85430 - Oct-06-2022

The values themselves have types, but the variables that refer to them do not. Python is dynamically typed, so a variable can refer to values of different types:

x = 1 # x refers to an integer
x = "foo" # now x refers to a string 
There are type hints - see the typing module, though as per the note in the docs, these aren't enforced.


RE: Variable Types in a class - Yoriz - Oct-06-2022

You could use a dataclass for this
from dataclasses import dataclass
from typing import Optional


@dataclass
class Node:
    value: float
    next: Optional["Node"] = None
    prev: Optional["Node"] = None



RE: Variable Types in a class - deanhystad - Oct-07-2022

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.


RE: Variable Types in a class - nafshar - Oct-07-2022

deanhystad - Your explanation of this issue is brilliant and very educational. I am most grateful for the time you took to explain it so clearly. Realizing "instance variables are really just entries in a dictionary" is foundational to understanding python. Not sure how you gained this deep insight into Python, but understanding it at these levels is required by all, especially me :)

Yoriz's suggestion of dataclass is also very helpful. Thank you Yoriz.


RE: Variable Types in a class - Yoriz - Oct-07-2022

If you are actively using a type checker the following code would flag an error
from dataclasses import dataclass
from typing import Optional


@dataclass
class Node:
    value: float
    next: Optional["Node"] = None
    prev: Optional["Node"] = None


node = Node(0.1)
node.string = "Hello"
For instance in vscode with the type checker mode set to at least basic, pylance would put a red line under string and hovering over it would show the following:
Error:
string: Literal['Hello'] Cannot assign member "string" for type "Node" Member "string" is unknown

Python version 3.10 added slots as a parameter to dataclass, The following can now be done
from dataclasses import dataclass
from typing import Optional


@dataclass(slots=True)
class Node:
    value: float
    next: Optional["Node"] = None
    prev: Optional["Node"] = None


node = Node(0.1)
node.string = "Hello"
this will give the same AttributeError: 'Node' object has no attribute 'string' error


RE: Variable Types in a class - deanhystad - Oct-07-2022

I'm not a fan of how decorators look but using @dataclass(slots=True) is so much cleaner than using __slots__. That is a great addition.