[PYTHON] I actually statically typed a suitable snippet with mypy

reference:

I've tried static type checking by adding the mypy type annotation to the code for which command I wrote earlier in Python.

A lot of standard library stubs are already defined in mypy, but I didn't have enough classes and methods for the argparse, operator, and itertools modules used in the which command, so I tried to define the stubs myself. I was a little unfamiliar with the definition of this stub.

stubs/argparse.py


from typing import Any, List, Sequence, Undefined

class Namespace:
    def __init__(self) -> None:
        self.commands = Undefined(List[str])
        self.is_all = Undefined(bool)
        self.is_silent = Undefined(bool)

class ArgumentParser:
    def set_defaults(self, **kwargs: Any) -> None: pass
    def add_argument(self, *args: Sequence[str], **kwargs: Any) -> None: pass
    def parse_args(self, args: Sequence[str], namespace: Namespace = None) -> Namespace: pass

stubs/itertools.py


from typing import Iterable, typevar

_T = typevar('_T')

class chain:
    @classmethod
    def from_iterable(cls, iterable: Iterable[Iterable[_T]]) -> Iterable[_T]: pass

stubs/operator.py


from typing import Any, Function, Sequence, typevar

_T = typevar('_T')

def itemgetter(item: _T, *items: Sequence[_T]) -> Function[[Any], _T]: pass

Set an environment variable to make your defined stub visible to mypy.

python


(mypy)$ export MYPYPATH=./stubs/

Actually, I wrote the type annotation while repeating the definition of the stub and the type check, so I was a little confused because it didn't work. I think it's a matter of familiarity.

Finally, I added the type annotation to the source of which command which_with_statically_typed.py.

The diff with the original code looks like this.

python


$ diff -u which.py which_with_statically_typed.py 
--- which.py	2014-12-26 12:22:31.000000000 +0900
+++ which_with_statically_typed.py	2014-12-26 16:06:28.000000000 +0900
@@ -7,8 +7,10 @@
 from os.path import join as pathjoin
 from operator import itemgetter
 
+from typing import List, Sequence, Tuple  # pragma: no flakes
 
-def search(cmd, paths, is_all=False):
+
+def search(cmd: str, paths: List[str], is_all: bool=False):
     for path in paths:
         for match in glob.glob(pathjoin(path, cmd)):
             if os.access(match, os.X_OK):
@@ -17,7 +19,7 @@
                     raise StopIteration
 
 
-def parse_argument(args=None):
+def parse_argument(args: Sequence[str]=None) -> argparse.Namespace:
     parser = argparse.ArgumentParser()
     parser.set_defaults(is_all=False, is_silent=False, commands=[])
     parser.add_argument(
@@ -29,23 +31,23 @@
         help="No output, just return 0 if any of the executables are found, "
              "or 1 if none are found.")
     parser.add_argument("commands", nargs="*")
-    args = parser.parse_args(args or sys.argv[1:])
-    return args
+    namespace = parser.parse_args(args or sys.argv[1:])
+    return namespace
 
 
-def main(cmd_args=None):
+def main(cmd_args: Sequence[str]=None) -> int:
     args = parse_argument(cmd_args)
     if not args.commands:
         print('Usage: python which.py cmd1 [cmd2 ...]')
         return 0
 
     env_paths = os.environ['PATH'].split(':')
-    result = []
+    result = []  # type: List[Tuple[int, List[str]]]
     for cmd in args.commands:
         founds = list(search(cmd, env_paths, args.is_all))
         result.append((0, founds) if founds else (1, [cmd]))
 
-    status_code = max(map(itemgetter(0), result))
+    status_code = max(map(itemgetter(0), result))  # type: int
     if not args.is_silent:
         cmd_paths = [paths for ret_val, paths in result if ret_val == 0]
         for cmd_path in chain.from_iterable(cmd_paths):

The entire source is located at misc / which.

Recommended Posts

I actually statically typed a suitable snippet with mypy
I made a fortune with Python.
I made a daemon with Python
I tried to implement a blockchain that actually works with about 170 lines
I made a character counter with Python
I drew a heatmap with seaborn [Python]
I tried a functional language with Python
What I did with a Python array
I made a Hex map with Python
I made a life game with Numpy
A typed world that begins with Python
I made a stamp generator with GAN
I made a roguelike game with Python
I made a simple blackjack with Python
I made a configuration file with Python
I made a WEB application with Django
I made a neuron simulator with Python
I made a stamp substitute bot with line
I made a competitive programming glossary with Python
I made a weather forecast bot-like with Python.
I made a GUI application with Python + PyQt5
I made a Twitter fujoshi blocker with Python ①
I want to make a game with Python
I made a crazy thing called typed tuple
[Python] I made a Youtube Downloader with Tkinter.
I get a UnicodeDecodeError when running with mod_wsgi
I made a simple Bitcoin wallet with pycoin
I made a LINE Bot with Serverless Framework!
I played with Diamond, a metrics collection tool
I made a random number graph with Numpy
I want to write to a file with Python
I tried using a database (sqlite3) with kivy
I made a bin picking game with Python
I made a Mattermost bot with Python (+ Flask)
I made a QR code image with CuteR