python-course.eu

4. Type Annotation Classes

By Bernd Klein. Last modified: 13 Jul 2023.

This chapter of our Python course is about annotating classes. So you will nt learn the thoery of Python classes and how to write proper Python classes. If you want to learn all about object oriented programming in Python, we recommend our course on OOP:

OOP

A Class

We saw in the following Python example a class with type annotations:

%%writefile example.py

class Rectangle:
    def __init__(self, length: float, width: float) -> None:
        self.length: float = length
        self.width: float = width

    def area(self) -> float:
        return self.length * self.width

    def perimeter(self) -> float:
        return 2 * (self.length + self.width)

r: Rectangle 
r = Rectangle(5.0, 3.0)
print(r.area())      
print(r.perimeter()) 

OUTPUT:

Overwriting example.py
!python example.py

OUTPUT:

15.0
16.0
!mypy example.py

OUTPUT:

Success: no issues found in 1 source file

Another Example

%%writefile example.py

from typing import List

class ShoppingCart:
    def __init__(self, items: List[str]) -> None:
        self.items: List[str] = items

    def add_item(self, item: str) -> None:
        self.items.append(item)

    def remove_item(self, item: str) -> None:
        self.items.remove(item)

    def get_items(self) -> List[str]:
        return self.items

cart: ShoppingCart = ShoppingCart(['cheese'])
print(cart.get_items())
item: str
for item in ['tea', 'coffee']:
    cart.add_item(item)
print(cart.get_items())
cart.remove_item('tea')
print(cart.get_items())

OUTPUT:

Overwriting example.py
!python example.py

OUTPUT:

['cheese']
['cheese', 'tea', 'coffee']
['cheese', 'coffee']
!mypy example.py

OUTPUT:

Success: no issues found in 1 source file

Live Python training

instructor-led training course

Enjoying this page? We offer live Python training courses covering the content of this site.

See: Live Python courses overview

Enrol here

Another Example

%%writefile example.py
from typing import Set

class Person:
    
    def __init__(self, name: str, age: int, hobbies: Set[str]) -> None:
        self.name: str = name
        self.age: int = age
        self.hobbies: Set[str] = hobbies

    def add_hobby(self, hobby: str) -> None:
        self.hobbies.add(hobby)

    def remove_hobby(self, hobby: str) -> None:
        if hobby in self.hobbies:
            self.hobbies.remove(hobby)

    def get_hobbies(self) -> Set[str]:
        return self.hobbies
    
    
person: Person = Person("Andrea", 25, {"Reading", "Music"})
print(person.get_hobbies()) 

person.add_hobby("Cooking")
print(person.get_hobbies()) 

person.remove_hobby("Gardening")
print(person.get_hobbies()) 
person.remove_hobby("Cooking")
print(person.get_hobbies()) 

OUTPUT:

Overwriting example.py
!python example.py

OUTPUT:

{'Music', 'Reading'}
{'Music', 'Cooking', 'Reading'}
{'Music', 'Cooking', 'Reading'}
{'Music', 'Reading'}
!mypy example.py

OUTPUT:

Success: no issues found in 1 source file

Yet Another Example

%%writefile example.py
from typing import Set

class Robot:
    city: str = "Hamburg"
    __forbidden_names: Set[str] = {'Henry', 'Edgar'}

    def __init__(self, name: str = 'Marvin') -> None:
        self.__name: str
        self.name = name

    @property
    def name(self) -> str:
        return self.__name

    @name.setter
    def name(self, name: str) -> None:
        if name in Robot.__forbidden_names:
            raise NameError("Not a Valid Robot Name")
        else:
            self.__name = name

    def say_hi(self) -> None:
        print(f"HI, I am {self.name} from {self.city}")
        
        
x: Robot = Robot("Petra")
print(x.name)
y: Robot = Robot("John")

OUTPUT:

Overwriting example.py
!python example.py

OUTPUT:

Petra
!mypy example.py

OUTPUT:

Success: no issues found in 1 source file

Live Python training

instructor-led training course

Enjoying this page? We offer live Python training courses covering the content of this site.

See: Live Python courses overview

Enrol here

Examples with Generic

In this example, the Box class is defined as a generic class using Generic[T]. The type parameter T represents the type of the item stored in the box.

The class has an __init__ method that takes an initial item of type T and assigns it to the item attribute.

The get_item method returns the item stored in the box, which has a return type annotation of T.

The set_item method takes a new item of type T and updates the item attribute accordingly.

%%writefile boxes.py
from typing import TypeVar, Generic

T = TypeVar('T')

class Box(Generic[T]):
    def __init__(self, item: T) -> None:
        self.item = item

    def get_item(self) -> T:
        return self.item

    def set_item(self, new_item: T) -> None:
        self.item = new_item

OUTPUT:

Overwriting boxes.py

You can create instances of the Box class with different types, depending on what type you want the item to be. Here's an example usage:

%%writefile example.py
from boxes import Box

box1: Box[int] = Box[int](42)
print(box1.get_item())  # Output: 42

box2: Box[str] = Box[str]("Hello")
print(box2.get_item())  # Output: Hello

box3: Box[float] = Box[float](3.14)
print(box3.get_item())  # Output: 3.14

#reveal_type(box1)
#reveal_type(box2)

OUTPUT:

Overwriting example.py
!python example.py

OUTPUT:

42
Hello
3.14
!mypy example.py

OUTPUT:

Success: no issues found in 1 source file

The LoggedVar class is designed to add logging capabilities to a variable, allowing you to track changes and accesses to its value. It serves as a wrapper around a variable, providing methods to set and retrieve its value while logging relevant information.

The class is useful in scenarios where you want to monitor and log the modifications or accesses to a variable, providing insights into its behavior, changes over time, or for debugging and troubleshooting purposes.

%%writefile example.py
from typing import TypeVar, Generic
import logging

T = TypeVar('T')

class LoggedVar(Generic[T]):
    def __init__(self, value: T, name: str, logger: logging.Logger) -> None:
        self.name = name
        self.logger = logger
        self.value = value

    def set(self, new: T) -> None:
        self.log('Set ' + repr(self.value))
        self.value = new

    def get(self) -> T:
        self.log('Get ' + repr(self.value))
        return self.value

    def log(self, message: str) -> None:
        self.logger.info('%s: %s', self.name, message)

# Example usage
debug_var = LoggedVar[str]('initial', 'debug_var', logging.getLogger('debugging'))

debug_var.set('updated')  # Logs: debug_var: Set 'initial'
print(debug_var.get())  # Logs: debug_var: Get 'updated'

debug_var.get()

x = LoggedVar[int](42, 'x', logging.getLogger('debugging'))
x.get()
x.set(42+5)
x.get()

OUTPUT:

Overwriting example.py
!python example.py

OUTPUT:

updated
!mypy example.py

OUTPUT:

Success: no issues found in 1 source file

Live Python training

instructor-led training course

Enjoying this page? We offer live Python training courses covering the content of this site.

See: Live Python courses overview

Enrol here