This code contains a huge type error, which mypy misses:
from dataclasses import dataclass
class WrongThing:
def do_wrong_way(self):
pass
class RightThing:
def do_right_way(self):
pass
@dataclass
class Holder:
thing: RightThing
def fill_thing(self):
self.thing.do_wrong_way() # <--- This is wrong
m = Holder(RightThing())
m.fill_thing()
Why doesn't mypy object that self.thing
lacks a do_wrong_way
attribute? Am I doing something, er, wrong?
mypy does correctly flag this code with an [attr-defined]
error:
r = RightThing()
r.do_wrong_way() # <--- [attr-defined] error
Let this be a lesson never to leave a function definition unannotated, even if it seems trivial!
Mypy passes this code because MyPy's default setting is to ignore functions that "have no type annotations", as it is assumed that the user wants these to be "dynamic functions" that the user doesn't want type-checked. This is mentioned in the documentation here.
In your Holder
class, you have no annotations in the signature of the fill_thing
method at all, so mypy just doesn't look at it at all. If you change your Holder class to this, so that fill_thing
is explicitly annotated as returning None
:
@dataclass
class Holder:
thing: RightThing
def fill_thing(self) -> None:
self.thing.do_wrong_way()
... Then Mypy raises an error, just as we'd expect!
How to avoid these errors in the future
I recommend always running mypy with the --strict
setting, as with strict=True
, MyPy will warn you if you leave any functions without type annotations. While the default setting of "we won't raise errors if you leave the function unannotated" is probably more beginner-friendly, I personally find it just leads to an unfortunate number of bugs if you don't use Mypy with the --strict
setting.
Another option, if you'd like unannotated functions to be checked, but don't want MyPy to be quite as strict as it is with the --strict
option, is to run MyPy with the --check-untyped-defs
option. A full list of command-line options can be found in the documentation here.