Search code examples
pythonpython-3.xabstract-classtype-hinting

Proper way to type-hint undetermined (abstract) class attributes?


I created an abstract class Animal for simillar classes (Cat, Dog, Horse ...) sharing simillar methods.

Also, I want to use the class Animal as an indicator of their common attributes(id, name, weight, ...) and their types as code below, but it looks so ugly to me.

from abc import ABC, abstractmethod
from typing import Optional


class Animal(ABC):
    def __init__(self) -> None:
        self.id: Optional(int) = None
        self.name: Optional(str) = None
        self.weight: Optional(float) = None
        self.height: Optional(float) = None
        self.age: Optional(int) = None
        ...

    @abstractmethod
    def cry(self) -> None:
        pass

Should I type hint all attributes as Optional, since their values are not determined (so I initialized them with None) in abstract class Animal? Are there any better way to type-hint undetermined class attributes?


Solution

  • If the goal is to say that any subclass of Animal should have the attributes id, name, etc., but not providing any defaults (which I assume is why you assigned them None), then you can declare them using class attribute annotations:

    class Animal(ABC):
        id: int
        name: str
        weight: float
        height: float
        age: int
    
        @abstractmethod
        def cry(self) -> None:
            ....
    

    Note that this will not provide you with guarantees that all subclasses must have these attributes. It's merely a hint to the programmer and the IDE & typechecker, and in most cases this will suffice.

    If you wish to be certain, you can define them as abstract properties, although it would be quite verbose:

    class Animal(ABC):
        @property
        @abstractmethod
        def name(self) -> str:
            ...
    
    # Subclasses can implement that as a simple class attribute...
    class Cat(Animal):
        name = "cat"
    
    # ... or as a proper property.
    class Dog(Animal):
        @property
        def name(self) -> str:
            return f"{self.breed} dog"