Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using mypy with with lazy initialization of instance attributes

UPDATE: trying to check/fill values in another function


I'm trying to use mypy in my projects, but many of the instance attributes I use are only initialized after __init__, and not inside it. However, I do want to keep the good practice of declaring all instance attributes at __init__, so I need some complicated solutions to make this work.

An example to how I want this to behave (currently mypy is complaining):

from typing import Optional

class Foo:
    def __init__(self, x: int):
        self.x = x
        self.y: int = None  # will initialize later, but I know it will be an int

    def fill_values(self):
        self.y = x**2

    def do(self) -> int:
        return self.x + self.y

Currently mypy complains about the assignment of self.y, and wants it to be Optional or None.

If I agree with it and change the line to self.y: Optional[int] = None, then mypy complains on the return value of do, because self.y might be None.

The only way I found around it is to add as assert before using self.y, like: assert self.y is not None, which mypy picks up and understands. However, starting each method with many asserts is quite hard. I have many such values, and usually one method that initializes all of them, and all other methods runs after it.

I understand that mypy is rightfully complaining (the method do can be called before fill_values), but even when I try to prevent it I can't get mypy to accept this. I can extend this example by adding more functionality but mypy can't infer this:

from typing import Optional

class Foo:
    def __init__(self, x: int):
        self.x = x
        self.y: int = None  # will initialize later, but I know it will be an int

    def fill_values(self):
        self.y = x**2

    def check_values(self):
        assert self.y is not None

    def do(self) -> int:
        if self.y is None:
            self.fill_values()
        self.check_values()
        return self.x + self.y

Any idea of a more elegant solution that multiple assert statements and Optional types which obscure the code?

like image 700
Beka Avatar asked Mar 30 '20 06:03

Beka


1 Answers

I have found this to work for me:

class Foo:
def __init__(self, x: int):
    self.x = x
    self.y: int # Give self.y a type but no value

def fill_values(self):
    self.y = x**2

def do(self) -> int:
    return self.x + self.y

Essentially all you are doing is telling mypy that self.y will be an integer when (and if) it is initialised. Trying to call self.y before it is initialised will raise an error and you can check if it has been initialised using hasattr(self, "y").

like image 113
Rory Sullivan Avatar answered Oct 15 '22 01:10

Rory Sullivan