[HN Gopher] Python Type Checker Comparison: Empty Container Infe...
       ___________________________________________________________________
        
       Python Type Checker Comparison: Empty Container Inference
        
       Author : ocamoss
       Score  : 48 points
       Date   : 2026-02-25 13:45 UTC (4 days ago)
        
 (HTM) web link (pyrefly.org)
 (TXT) w3m dump (pyrefly.org)
        
       | curiousgal wrote:
       | I can't help but find type hints in python to be..goofy? I have a
       | colleague who has a substantial C++ background and now working in
       | python, the code is just littered with TypeAlias, Generic, cast,
       | long Unions etc.. this can't be the way..
        
         | IshKebab wrote:
         | I strongly disagree. Python has actually done a decent job of
         | adding type annotations into the language IMO.
         | 
         | If you ignore the bit where they don't actually specify their
         | semantics anyway.
         | 
         | > this can't be the way..
         | 
         | The alternative is fragile and unmaintainable code. I know
         | which I prefer!
        
         | tialaramex wrote:
         | Typing is a relatively easy way for the human author and the
         | machine to notice if they disagree about what's going on before
         | problems arise. It is unfortunate that Python doesn't do a good
         | job with types, I was reading earlier today about the mess they
         | made of booleans - their bool type is actually just the
         | integers again.
        
           | nubg wrote:
           | > I was reading earlier today about the mess they made of
           | booleans
           | 
           | Can you elaborate on that?
        
             | IshKebab wrote:
             | He did - booleans are integers:                 >>>
             | isinstance(False, int)       True
             | 
             | A related screw-up is implicitly casting everything to
             | bool. A lot of languages made that mistake.
             | 
             | Overall I'd say they didn't do an _awful_ job though. The
             | main problems with Python are the absolutely abysmal
             | tooling (which thankfully uv fixes), the abysmal
             | performance (which _sometimes_ isn 't an issue, but it
             | usually becomes an issue eventually), and the community's
             | attitude to type checking.
             | 
             | Actually type checking code you've written yourself with
             | Pyright in strict mode is quite a pleasant experience. But
             | woe betide you if you want to import any third party
             | libraries. There's at least a 50% chance they have no type
             | annotations at all, and often it's _deliberate_. Typescript
             | used to have a similar problem but the Javascript community
             | realised a lot quicker than the Python community that type
             | hints are a no-brainer.
        
             | tialaramex wrote:
             | Because Python decided that (for the usual New Jersey
             | reason, simplicity of implementation) bool should just be
             | an integer type the Liskov criterion comes into play. If we
             | can X an integer and we've agreed bool is an integer => we
             | can X a bool. That's not what booleans are but hey, it's
             | sorta close and this was easier to implement.
             | 
             | So, can we add two bools together? Adding _booleans_
             | together is nonsense, but we 've said these are a kind of
             | integer so sure, I guess True + True = 2 ? And this
             | cascades into nonsense like ~True being a valid operation
             | in Python and its result is true...
        
               | jcgl wrote:
               | > So, can we add two bools together? Adding booleans
               | together is nonsense, but we've said these are a kind of
               | integer so sure, I guess True + True = 2 ? And this
               | cascades into nonsense like ~True being a valid operation
               | in Python and its result is true...
               | 
               | The bitwise negation is indeed janky and inaccurate, but
               | _True + True = 2_ is absolutely a valid thing to say in
               | boolean algebra. Addition mean  "or", and multiplication
               | means "and."
        
               | __mharrison__ wrote:
               | This is actually useful in pandas. It enables asking
               | questions like "what percent of cars get greater than
               | 40mph?"
        
             | tech2 wrote:
             | It's not entirely fair.
             | 
             | Prior to 2.3 Python didn't have booleans, just
             | "truthiness". In 2.3 they added the Boolean class as a
             | subclass of int (because of patterns of development it was
             | a pragmatic choice). True and False were introduced, but
             | they were able to be reassigned which could cause all
             | manner of fun. 3.x made them keywords which put a stop to
             | that but the int aspect remained.
        
         | wiseowise wrote:
         | What is the way in your opinion?
        
       | IshKebab wrote:
       | I think it would be worth mentioning that in normal use (strict
       | mode) Pyright simply requires you to add type annotations to the
       | declaration. Occasionally mildly annoying but IMO it's clearly
       | the best option.
        
         | veber-alex wrote:
         | It's not "mildly annoying".
         | 
         | I don't enable strict mode on multiple projects because people
         | don't want to type anything outside of function signatures.
         | 
         | Inferring the type from the first use is 100% the correct
         | choice because this is what users want 99% of the time, for the
         | rest you can provide type information.
        
       | jez wrote:
       | A more complicated version of this problem exists in TypeScript
       | and Ruby, where there are only arrays. Python's case is
       | considerably simpler by also having tuples, whose length is fixed
       | at the time of assignment.
       | 
       | In Python, `x = []` should always have a `list[...]` type
       | inferred. In TypeScript and Ruby, the inferred type needs to
       | account for the fact that `x` is valid to pass to a function
       | which takes the empty tuple (empty array literal type) as well as
       | a function that takes an array. So the Python strategy #1 in the
       | article of defaulting to `list[Any]` does not work because it
       | rejects passing `[]` to a function declared as taking `[]`.
        
       | Boxxed wrote:
       | My favorite part about the type annotations in python is that it
       | steers you into a sane subset of the language. I feel like it's
       | kind of telling that python is this super dynamic language but
       | the type annotations aren't powerful enough to denote all that
       | craziness.
        
         | reubenmorais wrote:
         | That's nice if you're starting from scratch, but if you have
         | existing code to deal with, you don't have the privilege of
         | ignoring the insane subset.
        
       | loevborg wrote:
       | FWIW, Typescript is using Strategy 2:
       | https://www.typescriptlang.org/play/?#code/GYVwdgxgLglg9mABM...
       | 
       | I'm a bit confused by the fact that the array starts out typed as
       | `any[]` (e.g. if you hover over the declaration) but then, later
       | on, the type gets refined to `(string | number)[]`. IMO it would
       | be nicer if the declaration already showed the inferred type on
       | hover.
        
         | bastawhiz wrote:
         | It depends on your tsconfig. An empty array could be typed as
         | never[], forcing you to annotate it.
        
           | loevborg wrote:
           | Which setting specifically? Can you repro in the typescript
           | playground?
        
           | wk_end wrote:
           | I don't believe this is correct. There's no settings that
           | correspond to that AFAIK, and it'd actually be quite bad,
           | because you could access the empty array and then get a
           | `never` object, which you're not supposed to be able to do.
           | 
           | https://www.typescriptlang.org/play/?#code/GYVwdgxgLglg9mABM.
           | ..
           | 
           | `unknown[]` might be more appropriate as a default, but
           | TypeScript does you one better: with OP's settings, although
           | it's typed as `any[]`, it'll error out if you don't do
           | anything to give it more information because of
           | `noImplicitAny`.
        
         | sheept wrote:
         | I agree, it's always been unsettling to see any[] on hover,
         | even though it gets typed in the end.
         | 
         | I think one reason might be to allow the type to be refined
         | differently in different code paths. For example:
         | function x () {             let arr = []             if
         | (Math.random() < 0.5) {                 arr.push(0)
         | return arr             } else {                 arr.push('0')
         | return arr             }         }
         | 
         | In each branch, arr is typed as number[] and string[],
         | respectively, and x's return type is number[] | string[]. If it
         | decided to retroactively infer the type of arr at declaration,
         | then I'd imagine x's return type would be the less specific
         | (number | string)[].
        
       | nimbus-hn-test wrote:
       | Enforcing explicit annotations in strict mode is a productivity
       | multiplier. It prevents `list[Unknown]` from polluting the rest
       | of the codebase, which is much harder to fix later.
        
       | tl2do wrote:
       | Is there a compile-to-Python language with built-in type safety,
       | similar to how TypeScript transpiles to JavaScript? I'm aware of
       | Mojo and mypyc, but those compile to native code/binaries, not
       | Python source.
        
         | exyi wrote:
         | Python does not need that, as it has built-in type annotation
         | support. The annotation is any expression, so you can in theory
         | express anything a custom type-only language would allow you
         | (although you could make it less verbose and easier to read).
         | 
         | However, the it IMHO just works much worse than TS because: *
         | many libraries still lack decent annotations * other libraries
         | are impossible to type because of too much dynamic stuff *
         | Python semantics are multiple orders of magnitude more complex
         | than JavaScript. Even just the simplest question: Is `1`
         | allowed in parameter typed `float`? What about numpy float64?
        
           | tl2do wrote:
           | Thanks for helping me understand. I wasn't aware of Python's
           | type annotation support. I did some quick research and
           | learned that type annotations don't cause compile errors even
           | when there are type errors. Is that why type checkers like
           | Pyrefly exist?
        
       | Sinidir wrote:
       | In the example given in the article i think the correct behavior
       | would have been to infer the type backwards from the return type
       | of the function. Is that not why mypy actually errors here?
        
       | brainzap wrote:
       | In early typescript I was too lazy and just set an inital value
       | and then zero the list
        
       | electroglyph wrote:
       | my wishlist for pyrefly: when using decorated functions, show the
       | underlying type hints instead of the decorators
        
       ___________________________________________________________________
       (page generated 2026-03-01 23:00 UTC)