Skip to content

Instantly share code, notes, and snippets.

@ttsiodras
Last active July 20, 2017 03:12
Show Gist options
  • Save ttsiodras/ab0956bde4596df1c0b2975f0cee8081 to your computer and use it in GitHub Desktop.
Save ttsiodras/ab0956bde4596df1c0b2975f0cee8081 to your computer and use it in GitHub Desktop.
Annotating Python code with types (for mypy-checking)

I just finished adding type annotations to a 10-year old python codebase (for type-checking with the magnificent mypy).

I had to do stuff like this:

find . -type f -iname \*A_mapper.py | while read ANS ; do
    cat "$ANS" | \
        sed 's/def OnBasic(\([^,:]*\), \([^,:]*\), \([^,:]*\)):$/def OnBasic(\1: str, \2: AsnBasicNode, \3: AST_Leaftypes) -> None:/' | \
        sed 's/def OnChoice(\([^,:]*\), \([^,:]*\), \([^,:]*\)):$/def OnChoice(\1: str, \2: AsnChoice, \3: AST_Leaftypes) -> None:/' | \
        sed 's/def OnSequence(\([^,:]*\), \([^,:]*\), \([^,:]*\)):$/def OnSequence(\1: str, \2: AsnSequenceOrSet, \3: AST_Leaftypes) -> None:/' | \
        sed 's/def OnSet(\([^,:]*\), \([^,:]*\), \([^,:]*\)):$/def OnSet(\1: str, \2: AsnSequenceOrSet, \3: AST_Leaftypes) -> None:/' | \
        sed 's/def OnSequenceOf(\([^,:]*\), \([^,:]*\), \([^,:]*\)):$/def OnSequenceOf(\1: str, \2: AsnSequenceOrSetOf, \3: AST_Leaftypes) -> None:/' | \
        sed 's/def OnSetOf(\([^,:]*\), \([^,:]*\), \([^,:]*\)):$/def OnSetOf(\1: str, \2: AsnSequenceOrSetOf, \3: AST_Leaftypes) -> None:/' | \
        sed 's/def OnEnumerated(\([^,:]*\), \([^,:]*\), \([^,:]*\)):$/def OnEnumerated(\1: str, \2: AsnEnumerated, \3: AST_Leaftypes) -> None:/' \
        > /tmp/foo && mv /tmp/foo "$ANS"
done

Fun! (for various definitions of "fun"). Felt a bit like writing Lisp macros :-)

On a more serious note - worth it. REALLY worth it. Found many bugs after I annotated with types...

I particularly enjoyed using Generics:

TSrc = TypeVar('TSrc')
TDest = TypeVar('TDest')
class RecursiveMapperGeneric(Generic[TSrc, TDest]):
    def MapInteger(self, srcVHDL: TSrc, ...
...
class FromVHDLToASN1SCC(RecursiveMapperGeneric[List[int], str]):  # pylint: disable=invalid-sequence-index
    def MapInteger(self, srcVHDL: List[int], ...

And taming dynamic creation while limiting the typeset was awesome:

U = TypeVar('U', int, float)
def GetRange(newModule: Module, lineNo: int, nodeWithMinAndMax: Element, valueType: Type[U]) -> Tuple[U, U]:
    ...
    rangel = valueType(mmin)
    rangeh = valueType(mmax)
    return (rangel, rangeh)

# Example use of 'tamed' creation of the type I want (in this case, 'int'):
def CreateOctetString(newModule: Module, lineNo: int, xmlOctetString: Element) -> AsnOctetString:
    return AsnOctetString(
        asnFilename=newModule._asnFilename,
        lineno=lineNo,
        range=GetRange(newModule, lineNo, xmlOctetString, int))

There are warts, of course - e.g. notice the 'pylint, shutup' comment in the first example, since pylint is not aware of typing constructs (yet) - but I must use it, of course; it's part of the "mandatory weapons" set :-) flake8, pylint, now mypy, and of course good old py.test-driven tests combined with coverage checking.

Anyway - mypy has raised Python back to being my go-to language for everything (when the decision is mine to make, that is). And I honestly think it's the best reason to migrate to Python 3. (No, the comment-driven stuff in Py2 are not as good-looking. And the way the code looks matters!)

Cheers, Jukka!

Discussion in /r/Python is here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment