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

I generally agree.

I'll add a couple more frustrating limitations to Python's typing:

1. You can define a function type somewhat clumsily (`Callable[[Arg1T, Arg2T], ReturnT]`), but if your callback uses keyword arguments (pervasive among Python programs), you're out of luck.

2. You can't define recursive types like JSON. E.g., `JSON = TypeVar("JSON", Union[str, int, None, bool, List[JSON], Dict[str, JSON]])`.

3. Getting mypy to accept third party definitions sometimes works perfectly and other times it doesn't work at all. You get a link to some troubleshooting tips that have never actually worked for me.

Beyond that, it's just the general usability issues that ultimately derive from Python's election to shoehorn a lot of typing functionality into minimal syntax changes (as opposed to TypeScript which can make whichever syntax changes it likes because it isn't trying to be valid JavaScript).

I think the idea was that they didn't want to introduce build system complexity by way of a compiler, which is an easy choice to criticize in hindsight but I might've made the same call. I haven't used TypeScript in vain so I can't say for certain, but the TypeScript grass certainly looks pretty green from the Python side. Moreover, on the Python side we aren't even absolved from build-time problems since we still have to fight tooth and nail to get Mypy to accept third party type annotations.

Mypy is a valiant effort, but like everything in the Python ecosystem, it's a problem made difficult by an accumulated legacy of unfortunate decisions and there just isn't enough investment to move it forward. I've since moved onto Go for everything I used to use Python for, and I haven't honestly looked back--Go solves all of my biggest Python pain points: performance, tooling [especially package management], and dealing with the low-quality code that you tend to get when your colleagues don't have a type system keeping you on the rails and Go doesn't impose many pain points of its own (generics, but at a certain point in your career you realize that "good code" is not "maximally abstract code" nor "maximally DRY code", and the actual valid use cases for generics are fewer and farther between). TypeScript piques my interest, but it seems a bit too complex and configurable and in particular I'm not looking forward to figuring out how to wire together a JavaScript build system. Maybe I'll try Deno if I hear enough positive feedback about it, but for now it's hard to beat `go build`. Rust seems cool but the borrow checker tax is too steep for my blood.



> 1. You can define a function type somewhat clumsily (`Callable[[Arg1T, Arg2T], ReturnT]`), but if your callback uses keyword arguments (pervasive among Python programs), you're out of luck.

I've personally never missed the ability to add keyword arguments to a Callable. If you want to anyway, I understand that at least mypy has syntax for this:

* https://mypy.readthedocs.io/en/stable/protocols.html#callbac...

* https://mypy.readthedocs.io/en/stable/additional_features.ht...

> Beyond that, it's just the general usability issues that ultimately derive from Python's election to shoehorn a lot of typing functionality into minimal syntax changes

Many syntax niceities will be introduced in upcoming Python releases. From the article:

* Union types are shortened to X | Y (PEP 604)

* (?) Optional types shortened to X? (PEP 645)

* Type Hinting Generics In Standard Collections (PEP 585) - Can use list[T], dict[K, V], etc in place of List[T] and Dict[K, V].

With those changes, you won't generally need to import the `typing` module at all anymore.

> there just isn't enough investment to move it forward.

Dropbox has dedicated engineers maintaining mypy. And of course a few volunteers such as myself. And Guido. :)


More annoying for our codebase is the inability to specify optional/default arguments with closures. We often use functions that return other functions and you can't persist the "optional state" without using a protocol. It would be great just to match the current state of vanilla defs with optional params.

For example:

  def make_fun1(a: int) -> Callable[[int, Optional[int]], str]:
      def fun(b: int, c: Optional[int] = None) -> str:
          if c:
              b += c
          return f"{a+b}"

      return fun

  fun = make_fun(1)
  fun(2)
Will give you `Too few arguments` for `fun(2)`

You can fix this with something like:

  class FunC(Protocol):
      def __call__(self, b: int, c: Optional[int] = None) -> str: ...

  def make_fun(a: int) -> FunC:  ...
But that that seems like unnecessary overhead, because the non-nested def case can happily understand the optional parameter.


> I've personally never missed the ability to add keyword arguments to a Callable. If you want to anyway, I understand that at least mypy has syntax for this

Neat, I didn't realize this. This is far from desirable, but nice to know it's possible.

> Many syntax niceities will be introduced in upcoming Python releases. From the article:

I mean, typing out "Union" and "Option" or even having to import generic types from the typing module aren't really the syntactic pain points I was referring to. It's more like "Where do I define the TypeVar for a generic method on a class? Do I define it inside of the class or at the top-level module? In either case, if I reference that variable in another method, does it imply that these two types are the same? No doubt there are answers, but Python is the only language in which I have to navigate these kinds of questions. Similarly, having to create a protocol for kwarg callbacks seems really heavy. These are the kinds of things I would like to see improved.

> Dropbox has dedicated engineers maintaining mypy. And of course a few volunteers such as myself. And Guido. :)

Yes, I didn't mean to suggest there was no investment, only that the progress seems really slow (also, I didn't realize Guido was a volunteer? I thought he was employed by DropBox to work on Python). How long have recursive types been in a holding pattern, for example? I don't mean to disrespect you or any of the other maintainers--no doubt you're doing great work.


I definitely prefer the TypeScript dev experience for larger/longer-term projects, all else being equal. You spend an hour or two configuring your build tooling and then don't really have to mess with it much after that. But I also never bother setting up TypeScript for JS one-offs or scripts (where I would bother to add Python type annotations in those cases), so make of that what you will.

Deno is currently trying to bridge that gap - putting TS directly in your interpreter - but right now it comes with some caveats around being a separate runtime with incompatible system APIs, unfortunately. We'll see if it takes off enough that that becomes less of an issue.

Aside:

> it's a problem made difficult by an accumulated legacy of unfortunate decisions and there just isn't enough investment to move it forward

This is funny to me because JS has an even bigger accumulated legacy of unfortunate decisions, it's just gotten an obscene amount of investment to move it forward despite all odds ;)


If your callback uses kwargs, you define a Protocol that describes __call__ with typed kwargs, and use that as the type. Yes, it’s clunky, but it’s possible to do.


Imagine ...

1. a -> b :: Function(a)[b]

   (a, k1=t1, k2=t2) -> b :: Function(a, k1=t1, k2=t2)[b]

2. JSON = RecTypeVar('JSON', lambda Self: Union[None, bool, int, float, str, List[Self], Dict[str, Self]])

huh!


> 2. JSON = RecTypeVar('JSON', lambda Self: Union[None, bool, int, float, str, List[Self], Dict[str, Self]])

You don't actually need this workaround—the problem is typechecker support, not syntax. You can use strings for forward definitions, like this:

  JSON = Union[str, int, None, bool, List["JSON"], Dict[str, "JSON"]]
What happens next depends on your typechecker. Mypy says this:

  error: Cannot resolve name "JSON" (possible cyclic definition)
Pytype says this:

  Recursive type annotations not supported yet
But Pyre accepts it and correctly uses it for typechecking.


In fairness I find (1) to even less readable than the existing Callable syntax, but yeah, it would be cool if Python could express callables with kwargs and recursive types.




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

Search: