1

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.

1 Answers1

0

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.

STerliakov
  • 4,983
  • 3
  • 15
  • 37
  • For `@dataclass`, I overlooked that I don't need assignments upfront. To state in my own words: I need to know all names in advance but not necessarily the values? – enabtay0s9ex8dyq Dec 26 '22 at 23:20
  • You need to know all names **and** types (if you're using type checkers). You can set a value later - but this will require more special handling (`None` as a valid option is most applicable IMO, because otherwise you cannot be sure that the attribute is set in fact - but then get ready to `assert`'s again). For dataclass, you'll need to either allow `None` or provide default values - otherwise you will be unable to instantiate you class without passing all arguments to it. The main thing is that you cannot add attributes "on the go" and feed it to type checker. – STerliakov Dec 26 '22 at 23:23
  • Thanks. That was a very clear explanation. – enabtay0s9ex8dyq Dec 26 '22 at 23:27