19

I am trying the typing hint introduced by Python 3.5 and got a problem by using local stubs as the typing hint with mypy.

The experiment I do is to create kk.py containing

def type_check(a):
    pass

Also, I put kk.pyi containing

def type_check(a: int):...

in the same directory. In this way, I tried to trigger the error of "incompatible types in assignment" by passing a string to type_check in kk.py. However, when I ran mypy kk.py, I get no error.

Thus I tried another way that mypy doc suggests, which is to set environment variable MYPYPATH to ~/some/path/stub and put kk.pyi in the directory. I didn't get any error again.

Anyone can help me on this?

Here is the mypy wiki on how to use a local stub.

S.B
  • 13,077
  • 10
  • 22
  • 49
Musen
  • 1,244
  • 11
  • 23

2 Answers2

19

I do not know why someone have voted down this question without answering it or commenting about why he/she disliked it, but here is the answer I figured out:

The stub file of mypy only works when importing a module. Thus, if you have

def try_check(a):
    pass

in kk.py, and

def try_check(a: int):...

in kk.pyi in the same directory with kk.py or in the directory that the MYPYPATH specifies, mypy will type check the python file if you import kk. It is, if you have

import .kk
kk.try_check('str')

in test.py and run mypy test.py, mypy will report the type conflict. However, it will not report the conflict if you have

try_check('str')

in kk.py.

You can type check functions in the program that contains the function definition If you write the typing hint explicitly in the definition of the function. For instance, you can write

def try_check(a: int):
    pass

try_check('str')

in kk.py and then mypy kk.py. Mypy will report the type conflict.

killerx
  • 45
  • 8
Musen
  • 1,244
  • 11
  • 23
  • 3
    Thanks! I was struggling with this exact issue. I really don't understand why mypy works like this. Any ideas? – Solomon Bothwell Sep 19 '17 at 07:20
  • 1
    @SolomonBothwell I feel the initial attempt to have this static check program for big modulo like Django, so it supposes the user will check things in a package rather than self-written programs. – Musen Sep 19 '17 at 20:51
  • 1
    You can absolutely type-check your own programs, there's just no need or reason to use separate stub files. If your program imports a third-party library that has no annotations, but you want to use them, that's when you need local stubs. – OJFord Nov 25 '17 at 16:47
  • 3
    @OJFord sure, as the documentation demonstrates, we can do it. However, the point of this post is HOW can do it with a separate stub file rather an WHY we want to do it. – Musen Nov 28 '17 at 01:34
  • 2
    @OJFord also I'd prefer using local stubs instead of in-place typehinting, as the syntax is quite bloated in its current state and kinda makes the code unpleasant to read. Not to mention the circular imports and the workarounds you need to apply for the case of more complex typehints. – Szabolcs Jul 12 '19 at 10:39
1

As the accepted answer mentioned, unfortunately Mypy doesn't use the X.pyi stub file against X.py itself when you run mypy X.py.

This issue is addressed here as well:
https://github.com/python/mypy/issues/5028

The only solution I found to check the module itself is using mypy.stubtest:

A common problem with stub files is that they tend to diverge from the actual implementation. Mypy includes the stubtest tool that can automatically check for discrepancies between the stubs and the implementation at runtime.

The usage is simple, Just run python -m mypy.stubtest X (X is the name of the module without .py extension)

Example:

$ python3 -m pip install mypy

$ cat library.py
x = "hello, stubtest"

def foo(x=None):
    print(x)

$ cat library.pyi
x: int

def foo(x: int) -> None: ...

$ python3 -m mypy.stubtest library
error: library.foo is inconsistent, runtime argument "x" has a default value but stub argument does not
Stub: at line 3
def (x: builtins.int)
Runtime: at line 3 in file ~/library.py
def (x=None)

error: library.x variable differs from runtime type Literal['hello, stubtest']
Stub: at line 1
builtins.int
Runtime:
hello, stubtest
S.B
  • 13,077
  • 10
  • 22
  • 49