14

I am using mypy to check my Python code.

I have a class where I set dynamically some attributes and mypy keep on complaining about it:

error:"Toto" has no attribute "age"

This is my code:

class Toto:
    def __init__(self, name:str) -> None:
        self.name = name
        for attr in ['age', 'height']:
            setattr(self, attr, 0)


toto = Toto("Toto")
toto.age = 10  # "Toto" has no attribute "age" :(

Obviously, there could be 3 ways to solve the issue

  1. Ignoring the issue with # type: ignore: toto.age = 10 # type: ignore #...
  2. Using setattr to set the age of toto: setattr(toto, "age", 10)
  3. Setting the attributes explicitly (self.age = 0 ...)

However, I am looking for a more elegant and systematic way at the class level.

Any suggestion?

2 Answers 2

6

I don't follow mypy well enough to know whether this is (still, or ever was) the ideal work around, but this issue and this part of the cheatsheet indicate that something like:

from typing import Any

class Toto:
    def __init__(self, name:str) -> None:
        self.name = name
        for attr in ['age', 'height']:
            setattr(self, attr, 0)

    def __setattr__(self, name:str, value:Any):
        super().__setattr__(name, value)

toto = Toto("Toto")
toto.age = 10

Will allow you to do what you're doing without mypy complaining (which it does, just tested).

Any could be more restrictive, but the types will be checked on both setattr() and "traditional" obj.attr = ... calls, so heads up.

Sign up to request clarification or add additional context in comments.

2 Comments

I read the Cheatsheet many times but my brain is not as efficient at 11 PM ;) Thanks a lot for the answer.
Doesn't this defeat the purpose of using mypy in the first place? by just making the type of all attributes Any you lose a lot of power to do type checking elsewhere, or push headaches into other code
1

The standard way to solve this is to add an annotation for the attributes on the class level:

from typing import reveal_type

class Toto:
    age: int
    height: int
    
    def __init__(self, name: str) -> None:
        self.name = name
        for attr in ['age', 'height']:
            setattr(self, attr, 0)

toto = Toto("Toto")
toto.age = 10

reveal_type(toto.name)    # reveals str
reveal_type(toto.age)     # reveals int
reveal_type(toto.height)  # reveals int

Alternatively you can add a block of code under a TYPE_CHECKING condition, which should contain something that a type checker can understand statically -- it is ignored at runtime. So this accomplishes something similar, but is not really needed here:

from typing import TYPE_CHECKING

class Toto:
    def __init__(self, name: str) -> None:
        self.name = name
        for attr in ['age', 'height']:
            setattr(self, attr, 0)
        
        if TYPE_CHECKING:
            self.age = 0
            self.height = 0

Note that in both ways there is no longer a single source-of-truth for the attribute/type definition (compared to e.g. self.name), so a certain (often manageable) effort is required to keep the runtime and static behavior in line.

---

The solution proposed in the accepted answer is really not ideal. It essentially disables type checking on setting attributes, which defeats the purpose of using a type checker in the first place.

For instance, the following code contains a number of (likely) bugs that are now all ignored by the type checker due to __setattr__ accepting an Any :

from typing import Any

class Toto:
    def __init__(self, name: str) -> None:
        self.name = name
        for attr in ['age', 'height']:
            setattr(self, attr, 0)

    def __setattr__(self, name:str, value: Any):
        super().__setattr__(name, value)

toto = Toto("Toto")

# Neither misspelling an attribute name nor assigning a wrong type
# is detected by the type checker.
toto.heigt = 100
toto.age = "this is likely not a valid value for age"

1 Comment

Thank you for the detailed answer. This seems indeed to be the way to go ... unless you need to have something that is totally created dynamically without a way to know in advance the name of the attributes.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.