mypy – Type-hint new attributes

Question:

I often use dict to group and namespace related data. Two drawbacks are:

  1. I cannot type-hint individual entries (e.g. x['s']: str = ''). Accessing union-typed values (e.g. x: dict[str, str | None] = {}) later needs assert statements to please mypy.
  2. Spelling entries is verbose. Values mapped to str keys need four extra characters (i.e. ['']); attributes only need one (i.e. .).

I’ve considered types.SimpleNamespace. However, like with classes, I run into this mypy error:

import types
x = types.SimpleNamespace()
x.s: str = ''
# 3 col 2 error| Type cannot be declared in assignment to non-self attribute [python/mypy]
  • Is there a way to type-hint attributes added after instantiation?
  • If not, what other structures should I consider? As with dict and unlike collections.namedtuple, I require mutability.
Asked By: enabtay0s9ex8dyq

||

Answers:

There is no way to type-hint attributes that are not defined inside class body or __init__.

You need to declare some sort of structure with known fields or keys and then use it. You have a whole bunch of options. First things to consider (as most similar to your existing attempt) are TypedDict and dataclass. TypedDict does no runtime validation and is just a plain dictionary during code execution (no key/value restrictions apply). dataclass will create an __init__ for you, but you’ll be able to set any attributes later (without annotation, invisible for mypy). With dataclass(slots=True), it will be impossible.

Let me show some examples:

from typing import TypedDict

class MyStructure(TypedDict):
    foo: str


data: MyStructure = {'foo': 'bar'}
reveal_type(data['foo'])  # N: revealed type is "builtins.str"
data['foo'] = 'baz'  # OK, mutable
data['foo'] = 1  # E: Value of "foo" has incompatible type "int"; expected "str"  [typeddict-item]
data['bar']  # E: TypedDict "MyStructure" has no key "bar"  [typeddict-item]


# Second option
from dataclasses import dataclass

@dataclass
class MyStructure2:
    foo: str
    
data2 = MyStructure2(foo='bar')
reveal_type(data2.foo)  # N: Revealed type is "builtins.str"
data2.foo = 'baz'  # OK, mutable
data2.foo = 1  # E: Incompatible types in assignment (expression has type "int", variable has type "str")  [assignment]
data2.bar  # E: "MyStructure2" has no attribute "bar"  [attr-defined]

Here’s a playground link.

Answered By: SUTerliakov
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.