Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

In defense of mypy et al, Typescript had some of the greatest minds of our generation working for a decade+ on properly typing every insane form found in every random Javascript file. Microsoft has funded a team of great developers to hammer away at every obscure edge case imaginable. No other python checker can compare to the resources that TS had.


It's even worse (for python).

TS might transpile to JS and can always be split into a js and type annotation file but is it's own language developed in tandem with the type check based on a holistisch approach to find how to type check then and then put it into the syntax and type checker.

Thats not true for python at all.

Python types where added as annotations to the language many years ago, but not in a holistic approach but in simplistic approach only adding some fundamental support and then extended bit by bit over the years (and not always consistently).

Furthermore this annotations are not limited to type checking which can confuse a type checker (through Annotated helps a lot, but is also verbose, wonder how long until there is a "Annotated" short syntax e.g. by impl @ on type or similar).

Basically what the type annotation feature was initially intended to be and what it is now differ quite a bit (dump example `list` vs. `List`, `Annotated` etc.).

This is made worse that a bunch of "magic" is deeply rooted in python, e.g. sub-classing `Enum`. Sure you have that in JS too, and it also doesn't work that well in TS (if you don't add annotation on the dynamically produced type).

Lastly TS is structurally typed, which allows handling a bunch of dynamic typing edge cases, while Python is, well, in-between. Duck-typing is (simplified) structural typing but `isinstance` is a common thing in Python and is nominal typing...

So yeah it's a mess in python and to make it worse there bunch of annoyances related to ambiguity or to many ways how to do a thing (e.g. re-exports+private modules, you can do that common coding pattern, but it sucks badly).


Why do you say that duck typing is simplified structural typing? Its relationship with structural typing is on a different axis. Duck typing is its dynamic-typing counterpart.

Python does support structural typing through protocols introduced in version 3.8. They are documented in https://typing.python.org/en/latest/spec/protocol.html.

As a demo, here is part of https://www.typescriptlang.org/play/typescript/language/stru... translated to Python:

  from dataclasses import dataclass
  from typing import Protocol

  class Globular(Protocol):
      diameter: float

  class Spherical(Protocol):
      diameter: float

  # In Python, we need to define concrete classes that implement the protocols.
  @dataclass
  class Ball:
      diameter: float

  @dataclass
  class Sphere:
      diameter: float

  ball: Globular = Ball(diameter=10)
  sphere: Spherical = Sphere(diameter=20)

  # These assignments work because both types structurally conform to the protocols.
  sphere = ball
  ball = sphere

  class Tubular(Protocol):
      diameter: float
      length: float

  @dataclass
  class Tube:
      diameter: float
      length: float

  tube: Tubular = Tube(diameter=12, length=3)

  tube = ball  # Fail type check.
  ball = tube  # Passes.
This is what Pyright says about it:

  Found 1 error.
  /scratch/structural.py
    /scratch/structural.py:37:8 - error: Type "Ball" is not assignable to declared type "Tubular"
      "Ball" is incompatible with protocol "Tubular"
        "length" is not present (reportAssignmentType)
  1 error, 0 warnings, 0 informations
Edit: And this is ty:

  error: lint:invalid-assignment: Object of type `Ball` is not assignable to `Tubular`
    --> structural.py:37:1
     |
  35 | tube: Tubular = Tube(diameter=12, length=3)
  36 |
  37 | tube = ball  # Fail type check.
     | ^^^^
  38 | ball = tube  # Passes.
     |
  info: `lint:invalid-assignment` is enabled by default
  
  Found 1 diagnostic


I'd go a step further and say that duck typing is more than just structural typing's dynamic counterpart. Because, again, that's confounding two different axes. Dynamic vs static describes when type checking happens and whether types are associated with names or with values. But it doesn't necessarily describe the definition of "type".

The real difference between structural typing and duck typing is that structural typing requires all of a type's declared members to be present for an object to be considered compatible. Duck typing only requires the members that are actually being accessed to be present.

This is definitely more common in dynamic languages, but I'm not aware of any particular reason why that kind of checking couldn't also be done statically.


If I understand correctly, defining the protocol like this forces the implementation classes to have the members as proper fields and disallows properties. If you define `diameter` as a property in the protocol, it supports both:

    from dataclasses import dataclass
    from typing import Protocol

    class Field(Protocol):
        diameter: float


    class Property(Protocol):
        @property
        def diameter(self) -> float: ...

    class Ball:
        @property
        def diameter(self) -> float:
            return 1

    @dataclass
    class Sphere:
        diameter: float

    ball_field: Field = Ball()
    sphere_field: Field = Sphere(diameter=20)

    ball_prop: Property = Ball()
    sphere_prop: Property = Sphere(diameter=20)

Pyright output:

    /Users/italo/dev/paper-hypergraph/t.py
      /Users/italo/dev/paper-hypergraph/t.py:27:21 - error: Type "Ball" is not assignable to declared type "Field"
        "Ball" is incompatible with protocol "Field"
          "diameter" is invariant because it is mutable
          "diameter" is an incompatible type
            "property" is not assignable to "float" (reportAssignmentType)
    1 error, 0 warnings, 0 information 
That is to say, I find Python's support for structural typing to be limited in practice.


It's not perfect, but runtime_checkable is a thing https://docs.python.org/3/library/typing.html#typing.runtime...

It doesn't actually preserve typing on the protocol's methods though


What annoys me is that every programmers who wish their favourite language / feature was as popular as Python and they choose to implement it in Python to make Python "better". Python was created as a dynamically typed language. If you want a language with type checking, there are plenty of others available.

Rust devs in particular are on a bend to replace all other languages by stealth, which is both obviously visible and annoying, because they ignore what they don't know about the ecosystem they choose to target. As cool as some of the tools written for Python in Rust are (ruff, uv) they are not a replacement for Python. They don't even solve some annoying problems that we have workarounds for. Sometimes they create new ones. Case in point is uv, which offers custom Docker images. Hello? A package manager is not supposed to determine the base Docker image or Python version for the project. It's a tool, not even an essential one since we have others, so know your place. As much as I appreciate some of the performance gains I do not appreciate the false narratives spread by some Rust devs about the end of Python/JavaScript/Golang based on the fact that Rust allowed them to introduce faster build tools into other programming languages' build chains. Rust community is quickly evolving into the friends you are embarrassed to have, a bit like any JVM-based language that suddenly has a bunch of Enterprise Java guys showing up to a Kotlin party and telling everyone "we can be like Python too...".


This argument doesn't make a whole lot of sense because nothing about type annotations constrains Python code at all. In fact because they're designed to be introspectable they make Python even more dynamic and you can do even crazier stuff than you could before. Type checkers are working very hard to handle the weird code.

Pydantic being so fast because it's written in Rust is a good thing, you can do crazy dynamic (de-)serializations everywhere with very little performance penalty.


> nothing about type annotations constrains Python code at all

Sorry, but this is just not true. Don't get me wrong, I write typed Python 99% of the time (pyright in strict mode, to be precise), but you can't type check every possible construct in the language. By choosing to write typed Python, you're limiting how much of the language you can use. I don't think that's a bad thing, but it can be a problem for untyped codebases trying to adopt typing.


It is literally true. You don't need to run a type checker.


And in the process, they ended up creating an extremely powerful type system that ~nobody outside of that original team can (fully) understand.


How many people understand the intricacies of any complex language or type system? Though I think one of the great things about TS is that you need to understand none of it in order to do `npm install @types/lodash` and get all the benefits.


IMHO they created type annotations, not a type system

and how you use the type annotations to indicate a type system is inconsistent and incomplete (e.g. NoneType vs. None for inconsistency and a lot of mess related to mataclasses (e.g. Enum) and supporting type annotations for them for incomplete)

the fact that even today something as fundamental as enums have issues with type checking _which are not just type checker incompetence_ is I think a good way to highlight what mess it is

or that `Annotated[]` was only added in 3.9 and has a ton of visual overhead even through its essential for a lot of clean definitions in modern python code (where for backwards compatibility there is often some other way, which can be de-facto wrongly typed but shouldn't be type linted, have fun type checkers).


Parent comment was talking about TypeScript, not Python.


It's not mypy issue. Comparing to TS python typehints (spec wise) are a joke. It's started as bolted on adhoc solution and evolved quite chaotically. For example [1]. TS doesn't require a special decorator (sic!) to make your custom classes to be picked up by type checkers.

Or how make a wrapper function with args and kwargs to pass through?

[1]: https://docs.python.org/3/library/typing.html#typing.datacla...


The dataclass decorator isn't there to make the type checkers understand the class. Its main purpose is to automatically implement trivial methods like the constructor, equality, repr, etc. The type hints make this more convenient, but something similar already existed with attrs.


It's literally stated in the PEP: https://peps.python.org/pep-0681/

Also: https://github.com/python/mypy/blob/501a07b45af8e44eda665e53...

Also did you know mypy ignores typing of class decorators? You simply can't return a different type other than type[thisclass].




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: