Mypy 1.6
mypy-lang.blogspot.com
mypy-lang.blogspot.com
A couple years ago I was in charge of choosing between the two, and I somewhat flippantly chose Pyright because it felt a lot faster (<5 second to do 30k lines) and had an easy integration with the editors people use at work (Pylance, coc-pyright).
Later, I realized that some popular libraries we use (django, numpy) have dedicated plugins for Mypy that you can't use with Pyright. So you have to look for Pyright-friendly type stubs or roll your own.
I've generally liked Pyright (as a side note, they have super responsive maintainers - ask a question on their GH, and they usually answer within a few hours), but I've been wondering if I am missing out with Mypy.
When using Pyright in large Django projects, it's helpful to add explicit type annotations in a number of places (like reverse relations in Models) but I've found this tends to improve the clarity of the resulting code anyway.
So far the only thing that bothers me about pyright is that it can't infer the type of collections based on later insertions.
So in something like:
lst = []
lst.append(1)
lst is of type list[Any]I also program in Rust so I kinda expect this to work :P
I asked the maintainer about this and this is apparently by design, though mypy handles this correctly IIRC.
Further, out of the box, ruff doesn't even replace isort. You have to specifically enable it to sort imports (`I`)[^3].
I personally think that ruff's defaults are much too conservative. I configure it with `ALL` and then ignore the rules I don't want. I _want_ upgrades of ruff which add new rules to find new things. I'll never know about the new rules if I have to follow its development. I fully expect `pre-commit autoupdate` to cause me some pain after I run it, and that's okay. Linters adding new rules are usually helping me make my code cleaner.
[^1]: https://docs.astral.sh/ruff/faq/#is-ruff-compatible-with-bla...
[^2]: https://docs.astral.sh/ruff/faq/#how-does-ruff-compare-to-py...
Mypy is catching up on speed though! Can't say I've tried any plugins.
It is much better written, much more correct, has fewer bugs, and it is the default in VSCode so less friction to set up.
In my experience it isn't hugely faster so I wouldn't say that is a big factor. The main benefit is that it is just very correct and has very few bugs (and when there are bugs they are fixed very quickly).
I would avoid Mypy if at all possible. I once tried to fix a bug in it and the codebase was very very hairy. Partly that's because it predates official type hinting support, and partly because it supports a load of hacks to allow incorrect types. Sometimes even the way you write the type affects how it is interpreted (e.g. `a # type: foo` is different to `a: foo`).
The only times I would choose Mypy is if you really need one of those plugins you mentioned, or if you are adding types to a legacy codebase and can't face seeing how bad it really is.
Now that we're in the mid-to-late stage of Python typecheckers, `pyright` has a better intercept and slope than `mypy`.
I think there'll be one last typechecker to rule them all written in Rust – but we're not there yet, so use `pyright`.
And one written in python could just use python for the grammar so it would have the advantage of always loading the code the same exact way as python does.
I often leave this running, so I can see live errors on every save:
watchexec -rc -e py -- dmypy runWe use mypy + [django-stubs](https://github.com/typeddjango/django-stubs) (in a huge Django + drf project at day job) which includes a plugin for mypy allowing it to recognize all reverse relations and manager methods. Mypy is still really rough around the edges. The cli args are poorly documented, and how they correspond to declarations in a mypy.ini / pyproject.toml is mysterious. Match-statements still have bugs even a year after release. Exclusion of untyped / partially typed files and packages we've had to solve with grep filtering mypy's output for our whitelisted set of files, as it's been unable to separate properly between errors you care about (in your own codebase) and errors in others code (dependencies, untypable dynamic python packages etc).
The largest issue IMO is that mypy tried to adapt a java / OOP style way of type system onto python, instead of recognizing the language's real power within duck typing and passing structural types around. Typescript chose the right approach here, modelling javascript the way it is actually written, favoring structural over nominal typing, instead of the archaic and now left-behind way of Java-style OOP that has influenced mypy.
There was a recently accepted PEP which allowed for limited dataclass transforms, enough to cover the @attr.s usecase for both mypy and pyright, but nowhere near expressive enough to cover django's models and ORM sadly. It's probably impossible / undesirable to allow for such rich plugins, so i see the future for proper pluginless typing to be more akin to how pydantic / normal dataclasses solve typing, by starting with a specification of the types, deriving its runtime implementation, instead of plugins having to reverse the type representation of a custom DSL.
If it’s actually possible to live in typed nirvana with Django I’ll bang my head against the wall some more.
We’re using Django 3.2 btw + FactoryBoy.
Actually there’s a number of annoyingly dynamic Python libraries out there where methods are created dynamically that it feels a bit like playing whack-a-mole. Is the situation like TypeScript where you need a MyLibrary.types.ts for each library?
Here's our plugin setup in mypy.ini in case this helps (Django 4.2 + drf 3.14)
[mypy]
plugins =
mypy_django_plugin.main,
mypy_drf_plugin.main
ignore_missing_imports = True
follow_imports = silent
no_implicit_optional = True
warn_unused_ignores = True
check_untyped_defs = True
disallow_untyped_defs = True
disallow_untyped_calls = True
warn_unreachable = True
strict_equality = True
allow_redefinition = True
show_error_codes = True
mypy_path = stubs
[mypy.plugins.django-stubs]
django_settings_module = 'mycompany.settings'
with packages: mypy==1.6.0
django-stubs==4.2.4
django-stubs-ext==4.2.2
djangorestframework-stubs==3.14.3
with this horror of a regex in make (because you'll get drowned in wrong type errors in all of the untype files, and errors get shown from imports even if you don't care about that imported file), add more file targets as necessary: FILES_TO_MYPY = $(shell ls mycompany/\*/validators.py mycompany/\*/services.py mycompany/\*/selectors.py mycompany/\*/managers.py | sort | uniq)
# 1)We have to grep out django-manager-missing like this until the following bug
# is fixed: https://github.com/python/mypy/issues/12987.
# 2) We grep out the line of `Found 95 errors in 16 files (checked 83 source
# files)` that now appears as we use follow-imports: silent, because there's a
# bug where errors from imported modules are counted against the total even
# though they aren't emitted. If any real errors appear we get them as a
# separate line anyways.
.PHONY: mypy
mypy:
@{ MYPY_FORCE_COLOR=${NOT_CI} $(VENV)/bin/mypy --config-file mypy.ini $(FILES_TO_MYPY) 2>&3 | grep -v 'django-manager-missing\|errors in'; } 3>&1 | tee $(HYRE_TESTS_OUTPUT_PATH)/mypy.stdout.txt
This allows you to get proper errors for things like model = MyModel.objects.get()
othermodel = model.othermodel_set.first()
reveal_type(othermodel) # correctly revealed to note: Revealed type is "Union[mycompany.importpath.models.OtherModel None]"
and even errors on typos like model = MyModel.objects.get()
othermodel = model.ooooothermodel_set.first() # revealed as MyModel has no attribute ooooothermodel_set, perhaps you ment othermodel_set
.One potential benefit of mypy is that it comes with mypyc, a compiler that leverages mypy's evaluation of types. Since pyright and mypyc are not exactly equal, it makes sense to use mypy if you want to use mypyc.
They all seem to have slightly different approaches and features, but it's hard to decide which is best for the job.
https://github.com/microsoft/pyright/blob/main/docs/mypy-com...
our main focus is to be able to work with unannotated and partially-annotated code, and treat it on par with fully annotated code.
Turns out that’s wrong so far. Tons of people advocating for the latter.
I quickly looked at the two repos. Pyright has 30 open issues. Mypy is at 2200. That is a simplistic metric, but an immense difference in any case.
apt install pyright
E: Unable to locate package pyright
apt install mypy
mypy is already the newest version (1.6.0-1). poetry add pyright
:)Also, what is Python lacking that other languages have? It's expressive and fast to develop, and with type hints and linters, reasonably robust.
My main complaint is performance, but I'll take the speed to write most days.
Abusing the walrus operator — Well, it is abuse then and hard to read.
I actually mean: https://www.gnu.org/software/guile/manual/html_node/Paramete...
I disagree that Go's insistence on verbosity improves readability. Each individual line may simpler, but the larger purpose of the code is obscured in all the "if err != nil" weeds.
My unpopular opinion is that Dart should get a lot more attention. Strong typing with null-safety, compiles to native, batteries included, and nearly as expressive as Python.
That's the difference between script and system programming. When writing scripts, one is only concerned about carrying out a certain task and if anything goes wrong the whole thing can bail. Systems are more robust. When things go wrong they cannot just fail. They need to recover gracefully and do something meaningful in the failed state.
Handing failure conditions is the primary purpose of system code. Irrespective of Go, there is something to be said about some kind of identifier that says "take note: this is the most important code in the application!" If that comes across as being 'weedy', you know that you really needed a scripting language, not a systems language.
Different tools for different jobs.
Which even in systems is unavoidable, to be fair. Sometimes failure just isn't important. The division between systems and scripts is not perfect. But, that will be the uncommon case in systems. It is not certain that really needs a symbol in which to draw attention. (An implementation may need a symbol, but that's beyond this discussion)
Rust's whole error handling, including the ? operator work great for embedded systems where Go isn't enough of a systems language to even run.
EDIT: I think you're confused. The ? operator says "I'm not handling this specially here". That doesn't mean the error is just propagated to the top like an uncaught exception in a Python script. It allows you any level of granular handling you'd like.
Plenty of go code doesn't handle errors, just pretends to but doesn't do anything meaningful when they happen.
That said, I hate Go for unrelated reasons, and love Python.
But we're talking about expected failure (network failure, hardware failure, etc.), not exceptional cases (programmer error). You would not want to carry 'expecteds' on exception handlers. Different tools for different jobs.
That said, systems transcend Go. The discussion isn't about Go, or Python, specifically.
And if that isn't enough, the answer is OCaml, F#, D, C#, Java, Scala, Haskell, Kotlin, C++,... definitely not going from horse to donkey in language features.
Note that Pyre does not support Windows (https://github.com/facebook/pyre-check/issues/554) and only provides wheels for x86_64 Linux and macOS (https://pypi.org/project/pyre-check/0.9.18/#files). While I don't use Windows, it makes me reluctant to adopt it. First, I want Windows users to be able to contribute to my projects that work on Windows. Mypy and (predictably) Pyright won't create an obstacle for Windows users. Second, the limited number of platforms makes Pyre seem a little internal-toolish.
It may still be worth it. As of today, actually, one of my projects has a Pyre branch. I want to see what it is like developing with it.
it works well and fast for a whole codebase though
Edited to add: when you read various recommendations, they're all discrete and don't have the overall context. e.g. I installed the VSCode suggested extensions, but then read a comment that I should really be using Ruff. OK, that's good, but if I have extensions X, Y, and Z, should I add extensions A, B, and C?
Caveats aside, I use pyright, ruff and black (although ruff will probably one day consume `black`, entirely) – that's it.
`ruff` centralizes a lot of the linter/fixer ecosystem (isort, autoflake, pylint and more). `pyright` is for GoToDefinition and type-checking. `black` is for sanity-checking what `ruff` spits out, and should hopefully not be necessary (for me) as a standalone package at some point in the near future.
Ruff is not 1.0 yet, so I wouldn't use it at work – but I use it in all my own things.
I appreciate you, internet person.
I must say I don't like JS, never have, likely never won't, but Typescript is a really pleasant language, even if JS underneath is more visible than I'd like. The mix of static analysis possible with TS vs added overhead of having to specify types (which can get very complex!) is just right. I welcome any and all efforts to make Python go this way - I'd be perfectly fine with transplanting TS type system as is.
There was a recent HN submission about it: https://news.ycombinator.com/item?id=34222407.
I use Pyright as my primary type checker and mypy as a secondary. (For example, Pyright won't run in Termux on my phone. I could likely fix this, but mypy just installs with pipx.) There is a document comparing Pyright and mypy in the Pyright repository: https://github.com/microsoft/pyright/blob/b09f35889a9cc9b262....
While they are usually a great benefit and easy enough to so, sometimes getting types just right can be more of an effort than it's worth - it's beautiful that in those cases you can still go "fuck it, I know what I'm doing".
Best of both worlds, really.
Except you miss out on massive performance gains and your software runs 10x slower because of runtime type-checking. It's actually the worst of both worlds.
Everything is stored on the heap, there is no cache locality for objects so every object property access has the performance of a linked list (or hash map, not good), and all method invocations require dynamic dispatch.
What this means is your code will be slow as fuck for no reason. If your code is 100% typed and you managed to find a fully compatible compiler that actually took advantage of the type annotations you could see massive speedups and significantly lower memory usage (we are talking order of magnitude improvements).
Other dynamic-typed-and-also-static-typed languages do allow for this. Many Common Lisp compiler implementations, for example, respect things like the (the) special form (https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node106.html), which acts as a static type assertion that allows the compiler to actually throw away the runtime sanity-checking (at the cost of producing actual unsafe code if the constraints of the form are violated).
The concept of "types" breaks down at this level much like the concept of "a dog" breaks down at the atomic level.
These days all of my use of Python is with type annotations that are validated by mypy as part of CI. It's very rare that I run into a Python library that doesn't have type annotations available. It's massively improved the stability of the Python code I'm working on. One indirect side effect is that static typing actively discourages people from poor design patterns such as **kwargs or passing around dicts/dataframes of data, since there's an obvious downside to doing so now.
It also depends on your use case. pandas is obviously very flexible and easy to use, so it might be worth the tradeoff to keep using it if your main use case is interactive Jupyter notebooks run by data scientists, or dealing with highly variable input data. But most of my Python is backend data pipelines that work on predictable input data, where reliability is important, so I default to dataclasses, only using pandas in rare situations with a lot of validation code surrounding it.
def foo(bar: dict[int, dict[int, str]]) -> list[tuple[str]]
In older (< 3.10 ?) versions you have to import the classes from the typing module, but other than that it’s the same. You can also define types with the Generic class if you’d rather.
With live checks while you write, it’s pretty easy to do this as you go.
TLDR; BE WARNED. this basket of bandaids is NOT a replacement for a strongly typed language. I would NOT recommend Python to teams or for services. No amount of mypy, pydantic, pyright, ruff, black, can convert python into a production language. This collection of bandaids is complex to maintain, upgrade, standardize, and has infinite edge cases where type annotation errors are just wrong.
The worst part about this basket is that you get all of the above features for free and are REQUIRED with go via gopls and gofmt. Gopls "is the official Go language server developed by the Go team." good luck achieving parity of this in python.
If you implement a service/app/anything production, use anything but a dynamically typed language like python.
Ah yes, because Python was never used in production ever, not at Google, not at Dropbox, not ever.
The "static typing above all else" cargo cult is getting ridiculous.
You should be horrified by Dropbox's blog post on python in production: https://dropbox.tech/application/our-journey-to-type-checkin.... because this problem only existed and required $Ms of dollars to fix (even hiring the core team behind Mypy) because Python is being used instead of a typed language.
But rejecting all dynamically typed language is a poor shortcut to take. Python, Ruby, Javascript, and others have a lot of success stories.
Python is great for single developer experience and going fast as a single developer. I use it every day, willingly, in jupyter notebooks. But at scale there are no success stories. You are zoomed in on the present success of "engineering miracles" and ignoring the big picture that the application would have moved faster if they had the foresight to choose a typed language.
Hence, why i'm saying be warned on following the path of mypy+black+pyright+ruff+etc., since you will be doomed to patching on top of a fundamental flaw of the language.
I highly recommend to avoid Python for production apps especially if it is your free will (and not a matter of refactoring or rewriting existing mistakes).
Do you believe this yourself?
of course python/ruby/javascript have succeed in bringing value to the world in general -- but in this thread we're focusing on production reliability, debugging, refactoring.
as we see, dropbox is proud to admit that they hired Mypy core developers and fund the project, but guess how much they spent on the mistake of using python?
twitter, famously an advocate for Ruby, spent an ungodly amount of engineering to work around ruby, hit a wall, and switched to JVM.
Just the fact that mypy and typescript exist and have exploded in adoption is proof that eng. teams very quickly hit a sand pit when using dynamic typed languages. Mypy being funded by dropbox, Typescript funded by microsoft, shows the level of investment needed to support these dynamic languages.
And thats because of the fundamental tradeoff of dynamic typing vs static typing. That part no one can disagree on.
The only disagreeable part I believe I'm saying is where does the dynamic typing scale limit happen. As a polyglot developer, working a lot with junior developers, I can say that dynamic typing hits a scale limit of feature velocity / production support / cost much faster than the general folk are aware of.
there's this common idea in management that new grads, SREs, "less experienced devs", can jump in and contribute. it's a falacy because the code becomes unmaintainable without a large investment (more unittests, refactoring the "buggy" code blocks with mypy type annotations, some SME who continues fixing prod bugs). a Python developer will shove their head in the sand with repl'ing, testing after deploy, and settle with this dev/ex while being completely unaware of the benefits of compiler errors and warnings.
Python has the same level of complexity as another language but you'll only see all of that dirty baggage in production as runtime exceptions.