11 ms·
Using Mypy in Production
- CiTyBear 4y agoReally like Mypy. I have coded many python micro service with different framework but my minimum core set is: - Black (formatting) - Isort (import order) - MyPy (typing) - Pylint (linting) Edit: s/unit test/linting
- ljvmiranda 4y agoSame. How do you use pylint for unit testing? I only use it in my IDE.
- CiTyBear 4y agoSorry, I meant linting. Editted
- codekansas 4y agoAs someone who owns multiple label makers and organizes my utensils by type when I put them into the dishwasher I really like Mypy. Cool writeup, good points. I think a lot of people (ML scientist people especially) who haven't been forced to use a type checker don't realize the productivity benefits of having a type checker running on your whole codebase - they only see the annoying slow-down of having to do things a particular way and having the type checker bug you for inconspicuous errors. Example: One pattern (which I don't think a lot of people are familiar with?) that I started adopting recently is the use of `Literal` for type-checking strings. For example, instead of something like (on closer reading I realized this was in the blog post as well, but I suspect maybe some ML people will have seen this specific case before) class ActivationType(enum.Enum): sigmoid = "sigmoid" tanh = "tanh" def get_activation(key: str | ActivationType) -> nn.Module: key = ActivationType[key] if key == ActivationType.sigmoid: return nn.Sigmoid() if key == ActivationType.tanh: return nn.Tanh() raise KeyError(key) you can do something like this instead: from typing import Literal ActivationType = Literal["sigmoid", "tanh"] def get_activation(key: ActivationType) -> nn.Module: if key == "sigmoid": return nn.Sigmoid() if key == "tanh": return nn.Tanh() raise KeyError(key) The advantage is that you can do something like act = get_activation("tahn") and Mypy will show an error for your typo (instead of having to run your code and eventually hit the `KeyError`). So if you're just trying to quickly implement an idea, you don't have to kill brain cells searching for typos. Of course, doesn't make a difference if your coworkers all use Vim and Emacs with no extensions...
- stavros 4y agoI love your username. The militants turn, startled.
- throwaway343244 4y ago> raise KeyError(key) This would be flagged as unreachable I believe. mypy/pyright also supports exhaustive checking with unions def get_activation(key: Literal['sigmoid', 'tanh']) -> nn.Module: match key: case 'sigmoid': return nn.Sigmoid() case 'tanh': return nn.tanh() There are limits to mypy/pyright's exhaustive checker.. it will fail if the union is in CNF, you will need to convert it to DNF. [1]: https://en.wikipedia.org/wiki/Disjunctive_normal_form https://en.wikipedia.org/wiki/Disjunctive_normal_form
- LtWorf 4y agoWith Literal you can also do this: @dataclass class Message: event: Literal['message'] msg: str @dataclass class File: event: Literal['file'] url: str typedload.load(data, File | Message) Where data is something like `{'event': 'message', 'msg': 'bla'}`. After the load, you can trust that your objects are well formed and let mypy do its thing. So types can be useful at runtime as well. Otherwise the alternative would be to use raw dictionary, which mypy can't check. pydantic does a similar thing but it uses its own different typing, so it needs a mypy plugin or it flags everything as wrong.
- evilsnoopi3 4y agoYou can also use TypedDict if you would normally use a raw dictionary but want the type checking.
- LtWorf 4y agoYes, but you still need a module like typedload to do the runtime checking. TypedDict performs no checking by itself at runtime. class A(TypedDict): a: int A(d=32) # Returns {'d': 32} typedload.load({'d': 32}, A) # TypedloadValueError: Value does not contain fields: {'a'} which are necessary for type A
- kungfufrog 4y agoI can't articulate specifically why but using typing in Python just feels like so much pain compared to other languages that have "opt-in" nominal typing syntax (PHP et al.). The workflow feels frustrating, the ecosystem seems diverse and has no clear "blessed" path, and I'm still confused about what is bundled by Python and what I need to pull in from an external source. I REALLY want to use mypy but by the time I've figured out how to pull it all together I probably could have finished the program I'm working on. The relevant factor here might be the size of Python programs I typically work on, somewhere between a few hundred lines and a few thousand. I'm glad other people are having success because hopefully that'll smooth the pavement for the next time I circle around and try to add some meaningful types to my Python programs.
- chc 4y agoI think something that might help you understand a little better is this: Python includes everything you need to write type hints, but it doesn't include any type checking functionality. They're making a dynamic language where you can write types if you want, but they don't really do anything. To check your types, you need to use a third-party typechecker like Mypy.
- kungfufrog 4y agoI appreciate your response but I'm actually a pretty experienced Python programmer. I get it conceptually, I've even used it in practice just to get a feel for it, I just found the whole experience _painful_!
- WesolyKubeczek 4y agoThe gods intend that you use those annotations with a checker and some IDE that supports autocompletions and highlighting based on those. They basically lure you into annotating types all over the place so that their products can work better. When you work in an editor that doesn’t support those fancy things, you’ll feel exhausted pretty soon.
- ReflectedImage 4y ago
- gabereiser 4y agoHuge fan of Mypy but you lost me at LOC worship. Why do we do this? The more LOC is somehow attributed to more features? That simply isn’t true. More LOC means two things. One, what you are trying to do with the language is pushing its limits. Or two, you don’t understand the domain. Most large monorepo’s I have seen fall into the latter category while few reside in the former. Game engines, mature enterprise software, and a few others are large (probably OP’s codebase too), but we seem to revel in the fact that we have so much code to grok. Sometimes conciseness is better than cleverness. Back to mypy. I’d throw in black as well. Combining black, flake8, mypy, on precommit has loads of advantages. It’s completely opinionated but I find it helps me write better Python code.
- charliermarsh 4y agoI actually agree with this. And I like your second point around LOC as a yellow-flag around poor domain understanding. I tried to acknowledge the deficiencies in LOC in the second paragraph... but it felt like a useful shorthand for conveying a sense of scale and complexity (at least to an order-of-magnitude) in a domain-agnostic way. Outside of Mypy, we use: black, isort, flake8, docformatter, and autoflake (to remove unused imports) -- all on pre-commit + enforced on CI. I'd like to see Black cannibalize more of that toolchain :)
- gabereiser 4y agoYeah, understandable. It's Python so things can easily get verbose. I've known too many engineers that see LOC as a measurement of complexity when those things aren't directly related. Complexity is usually unwrapped into a simplified model of things (human nature) but often there's 2x more plumbing code to cobble it back into complex mode to work with it. Not saying it's the case in your codebase but it's been what I've seen in large Python codebases. A complex domain is broken down into simple domain models then wrapped in services that add that complexity back because the original domain model was simplified from a business perspective already. Complexity to me comes from obtuse concepts. The business trying to cast too wide a net or something along those lines. Microservices help to concise that vision into reusable bits but its gets a bad wrap because of the context switching (which means your domains aren't well defined.) Keep using those tools though, they will save you so much time down the road when you enforce test coverage and fully lean in on that autoflake.
- ReflectedImage 4y agoBasically, they screwed up by using a monorepo with Python and have decided to try and badly paper over it by using a type checker. For reference, don't do either of those things in Python.
- chc 4y agoWhat do you feel makes Python less suited to monorepos than any other language? I've worked in Python-heavy monorepos and they seemed about like any other kind. And there is nothing wrong with using a type checker for your Python type annotations.
- ReflectedImage 4y agoMonorepos require static typing as a first class citizen of the language. "And there is nothing wrong with using a type checker for your Python type annotations." There is a lot wrong with that. Python is a dynamically typed language. If you are using a static type checker on a dynamically typed language, you are not taking advantage of the dynamic nature of the language. You are basically not writing Python code at all. This means you get all the disadvantages of using a dynamically typed language with none of the benefits. In summary, you have a made a very poor engineering decision.
- goodoldneon 4y agoThis is a terrible take. Using type annotations doesn’t mean Python is no longer a dynamically typed language
- ReflectedImage 4y agoIf you enforce them then you can't do duck typing, this means adding lots of boilerplate code to your code base, increasing the amount of bugs, since bugs are proportional to the total number of lines of code in the project. Typing related bugs that aren't caught in unit testing are very rare.
- andelink 4y agoMypy is so so helpful and I can’t imagine Python without it. (I mean, I can, I did, it just sucked.)
- daniel_rh 4y agoThis article mentions the woes of circular imports. I thought MyPy let you work around that by doing if False: around your imports eg a.py: import c if False: import b class X: def x(self): #type: () -> b.Y from b import something_that_returns_y return something_that_returns_y(self) b.py: from a import X class Y: pass def something_that_returns_y(x : X) -> Y: return Y() per https://github.com/asottile/flake8-typing-imports https://github.com/asottile/flake8-typing-imports
- blibble 4y agothat's pretty horrible a few more things like that and it will start to look like machine generated javascript
- charliermarsh 4y agoYeah that's correct! It's a valid workaround, and we end up doing that occasionally. (In Python 3.5 and newer it's `if TYPE_CHECKING` instead of `if False`.)
- Rekksu 4y agoAs the article mentions, the biggest problem by far with using mypy and the Python static typing ecosystem generally is the lack of third party support, even for big or new projects. Python's benefits are as much about the libraries available as they are the language itself, and unfortunately it's kind of lacking right now especially compared to e.g. TypeScript support in npm. Waiting for it to get better only goes so far; there isn't yet a cultural expectation around publishing typesheds for everything.
- tony 4y agoI am moving all my open source projects to `mypy --strict`. Here's the diff of adding basic / --strict mypy types: libvcs: https://github.com/vcs-python/libvcs/pull/362/files https://github.com/vcs-python/libvcs/pull/362/files, https://github.com/vcs-python/libvcs/pull/390/files https://github.com/vcs-python/libvcs/pull/390/files libtmux: https://github.com/tmux-python/libtmux/pull/382/files https://github.com/tmux-python/libtmux/pull/382/files, https://github.com/tmux-python/libtmux/pull/383/files https://github.com/tmux-python/libtmux/pull/383/files unihan-etl: https://github.com/cihai/unihan-etl/pull/255/files https://github.com/cihai/unihan-etl/pull/255/files, https://github.com/cihai/unihan-etl/pull/257/files https://github.com/cihai/unihan-etl/pull/257/files Perks: - code completions (through annotating) - typings can be used downstream (since the above are all now typed python libraries) - maintainability, bug finding + Easy to wire into CI and run locally Longterm, unsure of the return on investment. I do promise to report back if I find it's not worth the effort.
- deleted 4y ago[deleted]
- Mehdi2277 4y agoFor a while I used both mypy and pyright for my team’s codebase. After about half a year I eventually dropped mypy . I think type checking is valuable just that most of errors mypy detected pyright also caught and using newer type features often led to mypy false positives. I had trouble justifying using both when I could require my teammates to install pyright. Advanced type features tend to run into more bugs and while both are well maintained, pyright’s maintenance is magical. I do not know any other open source library that fixes bugs as fast (most bugs are fixed in under a week). The main thing that eventually forced decision was a flaky (depends on cache) mypy crash using paramspecs half a year ago. At time paramspec support was still in progress and there’s a good chance that specific issue is fixed. The main awkwardness of pyright is it’s node library and most python devs I work with don’t interact much with node. But my team has a bash script that installs all our dependencies including node as needed (nvm) which mostly works. One benefit is you can use pyright as an LSP and it works very convenient in vscode. Edit: 3rd party library lacking types is probably biggest issue. As my codebase is mostly typed by itself I’ve started gradually writing type stubs for library apis we use. Only writing stubs for small percent of what we use helps but there’s still a ton to add given codebase was started without types.
- charliermarsh 4y agoWoah, Pyright is written in Node? And it has its own [parser](https://github.com/microsoft/pyright/blob/b74be3b2cb2c5d35b960e8c6a06966d89b40c35a/packages/pyright-internal/src/parser/parser.ts https://github.com/microsoft/pyright/blob/b74be3b2cb2c5d35b9...)? That's really interesting. I wonder how it compares to Mypy on speed. > The main thing that eventually forced decision was a flaky (depends on cache) mypy crash using paramspecs half a year ago. At time paramspec support was still in progress and there’s a good chance that specific issue is fixed. I actually think I ran into this exact issue (ParamSpec-related, only fails when reading from cache, etc.), which led me to pin a Mypy development version for a while and was fixed recently.
- anuragsoni 4y ago> I wonder how it compares to Mypy on speed. I use pyright at work at my current job, and used mypy at my previous job. Pyright has been better in almost every aspect in my experience. Its been more robust, and performs a lot better than mypy at type-checking in a fairly large mono-repo.
- tony 4y agoPainpoint with type annotations: not being able to reuse "shapes" of data, e.g. struct-like fields such as TypedDict, NamedTuple, dataclasses.dataclass, and soon *kwargs (PEP 692 [1]) via TypedDict. Right now, there isn't a way to load up a JSON / YAML / TOML into a dictionary, upcast it via a `TypedGuard`, and pass it into a TypedDict / NamedTuple / dataclass. dataclasses.asdict() or dataclasses.astuple() return naïve / untyped tuples and dicts. Also the factory functions will not work with TypedDict or NamedTuple, respectively, even if you duplicate the fields by hand [2]. Standard library doesn't have runtime validation (e.g. pydantic [3]). If I make a typed NamedTuple/TypedDict/dataclass with `apples: int`, nothing is raised in runtime when a string is passed. Other issues you may run into using mypy: - pytest fixtures are hard. It's repetitious needing to re-annotate them every test. - Django is hard. PEP 681 [4] may not be a saving grace either [5]. Projects like django-stubs don't give you completions, it'd be a dream to see reverse relations in django models. - Some projects out there have very odd packaging and metaprogramming that make typing and completions impossible: faker, FactoryBoy. [1] https://peps.python.org/pep-0692/ https://peps.python.org/pep-0692/ [2] https://github.com/python/typeshed/issues/8580 https://github.com/python/typeshed/issues/8580 [3] https://github.com/pydantic/pydantic https://github.com/pydantic/pydantic [4] https://peps.python.org/pep-0681/ https://peps.python.org/pep-0681/ [5] https://github.com/microsoft/pyright/blob/8a1932b/specs/dataclass_transforms.md#django https://github.com/microsoft/pyright/blob/8a1932b/specs/data...
- charliermarsh 4y agoThe "shapes of data" thing resonates a lot especially coming from TypeScript. The runtime validation stuff has been interesting to watch. Do you use Pydantic? It's very popular but I have a hard time getting over its willingness to cast / coerce implicitly (if I mark a field as an int, and pass in a str, I want an error -- is that weird of me?).
- stevesimmons 4y agoYou can type it as StrictInt etc to stop this implicit type conversion. https://pydantic-docs.helpmanual.io/usage/types/#strict-types https://pydantic-docs.helpmanual.io/usage/types/#strict-type...
- whoopdeepoo 4y agoWould be interesting to seem them try pyright on their codebase. IME pyright is faster, catches more potential bugs, and doesn't require its own custom plugin system (which seems to be a major burden on other libraries).
- rzimmerman 4y ago> Mypy catches bugs 100% yes. It’s much better than the examples would lead you to believe. Mypy catches stuff like: def f(arg: Optional[Object]): arg.method() # type error if arg is not None: arg.method() # ok It’s half the goodness of what you’d get from a well-typed language like Haskell or Rust but with the ability to say “trust me on this” and disable type checks for a line or two. Honestly I wish go tooling checked for nil pointer use as well as mypy unwraps optional values. Every time I add type hints and use mypy I find bugs. I would never make the case that type hints are better than a strongly typed language (especially with pattern matching), but it’s a great balance when writing python.
- staticassertion 4y agoThis 'flow typing' / TypeGuard approach is really awesome and probably the best part about languages like typescript/mypy. I wish that other languages would follow suit so that I can incrementally narrow the types in my program through conditionals.
- leni536 4y agoWhile it is cool and useful, it is unsound. https://mypy-play.net/?mypy=latest&python=3.10&flags=strict&gist=6e2532267d6ecca04896c1223473b3f9 https://mypy-play.net/?mypy=latest&python=3.10&flags=strict&... https://www.online-python.com/V5YRZJAWlk https://www.online-python.com/V5YRZJAWlk
- staticassertion 4y agoNice, that's a cool example. But that's not fundamental to the concept of flow typing, it's just a mypy limitation.
- planede 4y agoAnd a typescript limitation? https://www.typescriptlang.org/play?#code/MYGwhgzhAEDC0G8BQ1XQB4C5oDsCuAtgEYCmATtAD654gjQC80ADANxIC+SSAZnjsAAuASwD2OaD1GiAFMGywAlNgBuo4QBNEKNMAB06RjTrsuvfkLESiYMnIXKaxctrTRhPaHIOMGTfHSKrm5oZCSCeGQSbDqoXG5SssCK7G5hEVHQ+uim3CDhGNj4zhRMNnY4JADucDKKKdzA4hCi+XogogDmMugpQA https://www.typescriptlang.org/play?#code/MYGwhgzhAEDC0G8BQ1...
- rmnclmnt 4y ago> I typically show candidates a snippet that uses typing.Protocol as part of a broader technical discussion, and I can’t recall any candidates having seen that specific construct before I think the `typing.Protocol` [1] (aka "structural subtyping" or "static duck typing") does not get enough spotlight! This is one of the keys to migrate a very pythonic codebase to type hints, and allows to avoid infinite type hints shenanigans all over the place. Of course, MyPy supports this feature natively [2]. [1] https://docs.python.org/3/library/typing.html https://docs.python.org/3/library/typing.html [2] https://mypy.readthedocs.io/en/stable/protocols.html https://mypy.readthedocs.io/en/stable/protocols.html
- diarrhea 4y agoI had looked into protocols the other day but found no advantages over ABCs. In fact, failing to implement an ABC's interface is a 'compile-time' error, which is impossible to miss. Using protocols and mypy would be a soft-fail in comparison, where code runs but can fall flat at runtime still, if mypy is simply ignored/forgotten (it's optional). I guess implementing multiple protocols (interfaces in other languages like C#) is possible and more awkward using ABCs?
- saila 4y agoThere's some info in the PEP for protocols about their advantages versus other approaches: https://peps.python.org/pep-0544/#rationale-and-goals https://peps.python.org/pep-0544/#rationale-and-goals https://peps.python.org/pep-0544/#existing-approaches-to-structural-subtyping https://peps.python.org/pep-0544/#existing-approaches-to-str... As to mypy being optional, it's easy enough to make it required via git hooks and/or CI. If your process doesn't make this easy for mypy or any other tool (e.g. formatting), I'd wager there are more fundamental issues.
- learndeeply 4y ago> My unsubstantiated guess is that this is one of the most comprehensively-typed Python codebases out there for its size. Not important, but FAANG companies have several orders of magnitude more strictly-typed Python than this.
- j4mie 4y agoThe more I think about it, the more I think that the controversy around static type annotations in Python boils down to this: Improved readability This is very subjective, and is particularly sensitive in a language like Python which (rightly) has such a strong historical emphasis on readability above almost anything else. My personal opinion is that static type annotations are extremely detrimental to readability. They add jarring line noise that makes reading Python much less like reading English. Hitting a type annotation when reading Python forces my brain into little backtracking loops which hugely diminishes my ability to form a mental model of the code from a quick read. I wonder if people who come to Python from other languages (that are already statically typed) are accustomed to the poor comprehension introduced by types, and so don't experience this drawback.
- tajd 4y agoI came from Python to Scala and I'm not sure I'd move back in a hurry. I like types annotations as it helps me understand what exactly is being passed between functions - sometimes that's no always clear from the code. I think "readability" is subjective so I just thought I'd throw my own opinion in there.
- allendoerfer 4y agoI want to develop Python at 80 characters per line, inside vim. I enjoy the elegance and simplicity and frankly the whole culture around it. Libraries, ecosystems and actual business reasons that matter aside, if I cannot have that, I would rather use something like Kotlin instead. PHP does it better. Types are an add-on, but integrated into the interpreter. Also it has never been pretty in the first place, so nothing was lost.
- a4a4a4a4 4y ago> Hitting a type annotation when reading Python forces my brain into little backtracking loops which hugely diminishes my ability to form a mental model of the code from a quick read. This makes me think you've never dealt with untyped Python code in a large production repo. The frustration seeps in the hundredth time you encounter an untyped `get_user_ids(...)` written by a co-worker (or yourself in the past), and wonder "Hmm, is this going to return a list of ints? Strings? UUIDs? A generator of those things?" and then you have to dig into the function to find out what it's actually going to give you, and then each function inside of that will have the same issues, and pretty soon you're building a mental model of a call stack to discern types anyway, you'll be happy to embrace the type system of python. "Readability" doesn't matter if you're unable to simultaneously quickly build a mental model of what is actually happening in the code. Sure, `def get_user_ids(...)` is super readable in that I quickly understand "this is going to give me some user ids", but that's useless when you consider the immediate next step of "what format are they in, so I know what I can do with them afterwards". `def get_user_ids(...) -> Optional[Iterable[int]]` is infinitely more useful, because I know that I can't use the function unchecked inside a list comprehension, because it might return `None`. And if it doesn't return `None`, I know I have, for example, integer user ids which I can compare with `==`, vs a custom type which might not implement `__eq__`.
- miiiiiike 4y agoI've been using Python since 2008 and the type annotations are the thing that broke me. I don't want to configure another tool on every project just to get (incomplete) type checking. Why do I have to pick a type checker? Just check my types. `typing.Protocol` is a poorly designed `Interface`. `abc` is a band-aid over missing `abstract` class/method syntax. Things that should be part of the language are left to libraries. Just add interfaces, enums, and abstract classes/methods to the language. I'm helping a new developer learn Python and having to explain all of the hoops I've been jumping through for the past 15 years is embarrassing. Making things "simpler" is making things more complex. My current project is going to be my last Python project. I'm tired of add-ons and hacks, I want a complete language.
- cillian64 4y ago> Making things "simpler" is making things more complex. This hits the nail on the head for me with Python. The standard libraries take a very “batteries included” approach but the language constructs don’t, and that means people use the flexibility of the language to do things their own way. I find that really increases my mental workload when reading other people’s software (how have they done this) and when writing my own (which way should I do this).
- miiiiiike 4y agoWell said.
- kmbfjr 4y agoThink they’re using a monorepo?
- radus 4y agoI really really wanted to like mypy but my experience with mypy and Django has been very poor - it is slow, type inference is not good and most of the errors are false positives. Perhaps I’m spoiled by Typescript or django-stubs is just not quite mature enough.
- BiteCode_dev 4y agoMypy is very useful on big projects, and does catch bugs regularly in my code. The ergonomics improved a lot and it's now usable, so the cost ratio/benefit is worth it today. But barely. Even assuming you use the latest Python version (lots of project can't), you still have to import tons of things you use all the time like Iterable, Self, Callable and so on. Then you have to deal with with the poor Protocol solution for duck typing, aggressive defaults, mypy slowness (before 9.13 it's terrible, after it's just bad and mypyd is quickly mandatory) and a surprisingly high number of bugs (such frustrating time wasters). Add on that false positives, low support from some popular libs and incompatible type checker implementations, and you get a very much meh experience. Very far from the awesomeness on Python. If you are unlucky and have to use anaconda, mypy dependencies make it extra fun to include. Still, I'm glad it exists. It's still very useful. But thank god hints are optional.
- noitpmeder 4y agoI totally agree that importing type classes are one of the worst parts of the system. I can't wait to move everything to 3.10+