Использование Annotated для хранения метаданных

Зачастую нам необходимо сохранить некие метаданные, описывающие атрибуты объекта.

Например, мы хотим описать размерности полей объекта, чтобы можно было показать значения вместе с размерностями:

MEASURES = {
    "n1": "Hz",
}

class PrettyNumbers:
    n1: Annotated[int, "Hz"] = 10000.1
        
    def measure(self, attr_name: str) -> str:
        return MEASURES[attr_name]
        
    def __repr__(self) -> str:
        return f"{self.n1} {self.measure('n1')}"
    
print(PrettyNumbers())

В результате будет напечатано 10000 Hz.

Проблема в примере выше в том, что у нас в разных местах описаны атрибуты и их метаданные. Это и неудобно и чревато ошибками

Начиная с Python 3.9 появилось Annotated. В первую очередь это предназначено для библиотек типа Pydantic но никто не запрещает нам использовать это и в наших приложениях.

Например, вот так можно переписать пример выше, используя Annotated:

from typing import Annotated, get_type_hints

class PrettyNumbers:
    n1: Annotated[int, "Hz"] = 10000.1
        
    def measure(self, attr_name: str) -> str:
        return get_type_hints(PrettyNumbers, include_extras=True)[attr_name].__metadata__[0]
        
    def __repr__(self) -> str:
        return f"{self.n1} {self.measure('n1')}"
    
print(PrettyNumbers())