[HN Gopher] Are you expected to run five Python type-checkers now?
       ___________________________________________________________________
        
       Are you expected to run five Python type-checkers now?
        
       Author : ocamoss
       Score  : 140 points
       Date   : 2026-06-08 12:24 UTC (15 hours ago)
        
 (HTM) web link (pyrefly.org)
 (TXT) w3m dump (pyrefly.org)
        
       | woeirua wrote:
       | With agents it no longer makes sense to tie yourself to Python's
       | archaic development experience. How many type checkers are there?
       | Package managers? Don't even get me started on cross-platform
       | deployment.
       | 
       | Strongly typed, compiled languages have never been easier to use,
       | and agents reap huge benefits from the tight feedback loop that
       | the compiler provides. Moreover the benefits of the Python
       | ecosystem are less significant today than anytime in the past 20
       | years. Need something that's only available in Python? Just point
       | some agents at it and you can port it.
        
         | voidUpdate wrote:
         | What about the several people worldwide who don't want to use
         | LLMs to program?
        
           | cryptonym wrote:
           | They also "reap huge benefits from the tight feedback loop
           | that the compiler provides".
           | 
           | When something is easier/requires less context, it tends to
           | work well for both human and LLM.
        
             | vips7L wrote:
             | I've noticed this a lot in LLM generated Java. Since it
             | doesn't know what can or can't be null it tends to wrap
             | everything in Optional<T>. Super strong type systems are
             | becoming even more important.
        
               | hedora wrote:
               | You probably need to tell it to rip as many of those out
               | as possible (and replace them with null annotations).
               | 
               | I've noticed LLMs sometimes pick a documented anti-
               | pattern (passing Optional around in Java is not
               | recommended), then amplify it (like a human might).
        
               | vips7L wrote:
               | That's because LLMs suck.
        
         | datsci_est_2015 wrote:
         | > Just point some agents at it and you can port it.
         | 
         | Don't think we're there yet, otherwise we would see a bunch of
         | forks of major libraries to alternative languages - and not
         | just Python. There's still too much risk of insidious errors
         | and bugs.
        
           | hedora wrote:
           | I've done thus a few times for stuff in the < 10,000 LOC
           | space. It works great.
           | 
           | There's something particularly satisfying about shipping a
           | 1-10MB static rust binary instead of a 2GiB docker python
           | environment.
           | 
           | (I'm talking about just porting simple applications, or maybe
           | a missing package/crate at a time. Not both at once, and not
           | typical 100K-10M line internal legacy sprawl)
        
             | datsci_est_2015 wrote:
             | What have you ported, for example? Any 3PL or all internal
             | code?
        
       | voidUpdate wrote:
       | > "In Python, any method __eq__ is expected to return bool, and
       | if it doesn't, then we need to explicitly tell type-checkers to
       | ignore the type error. This function in Polars can also return
       | different types depending on the inputs, thus requiring
       | overloads."
       | 
       | Why would you ever want a == b to not return a bool??
       | 
       | EDIT: Yes, I understand that you can do element-wise equality
       | checks on numpy arrays now
        
         | datsci_est_2015 wrote:
         | One example is if an and b are arrays (e.g. numpy arrays) it's
         | not unreasonable for dunder eq to return an array of booleans.
         | 
         | Another example might be if you have a domain specific
         | representation of equality (e.g. class Equality)
        
           | voidUpdate wrote:
           | I can see the first one making sense, but why would you need
           | a representation of equality other than "yes, these are
           | equal" and "no, these are not equal"?
        
             | datsci_est_2015 wrote:
             | Well personally I'm not a fan of turning everything into an
             | object, but if you have properties or methods that exist
             | upon the concept of Equality you might want to encode
             | directly onto a class. Maybe in a domain where "Equality"
             | is an important concept, like mathematics or even something
             | like accounting.
             | 
             | Could enable a different interface into approximate
             | equality for floating point numbers:
             | Equality.approximate(iota: float) -> bool
        
             | agons wrote:
             | The first use case that comes to mind is if you want a DSL
             | to build expressions that are evaluated later in some
             | different context e.g. when using `polars`:
             | 
             | ```python df.filter( pl.col("foo") == pl.col("bar"), ) ```
             | 
             | Sqlalchemy does something equivalent too, and I'm sure
             | there are many others.
        
         | xemdetia wrote:
         | I thought JavaScript language equality quirks was seen as
         | problematic not a missing feature in Python.
        
           | voidUpdate wrote:
           | At least in javascript, it tells you if things are equal or
           | not. In python, apparently you could answer if A is equal to
           | B with "beans" or 17 or ['a']
        
             | hmry wrote:
             | Never understood this complaint about operator overloading.
             | 
             | In any language, a function called `isEqual` could wipe
             | your hard drive and replace your wallpaper with a photo of
             | a penguin. Therefore, letting programmers pick the names of
             | their functions is bad? No, obviously naming things for
             | least surprise is the programmer's responsibility.
             | 
             | But when it's the symbols `==` instead of an ASCII name,
             | it's a problem in language design?
             | 
             | (FWIW in Javascript, being unable to override == is
             | actually a problem when you want to use objects as Map
             | keys)
        
           | throwaway894345 wrote:
           | Python never met a footgun it didn't need to adopt. In this
           | case, however, it's not equality checks, but operator
           | overloading. I was a Python developer for a decade before
           | switching to Go and life on this side is so much better.
        
             | data-ottawa wrote:
             | Operator overloading has never been an issue for me, but
             | terminating a line with a comma creating a tuple, or white
             | space (including new lines) between strings to concatenate
             | have cost me days of work over the years.
             | 
             | I understand why those exist, but they're pure evil.
        
         | samsartor wrote:
         | Elementwise equality! Given two dataframe columns or ndarrays,
         | users often expect `==` to give out a column or ndarrays of
         | bools (like `+`, ` _`, `*, ` &`, and just about every other
         | binary operator)._
        
           | stephbook wrote:
           | What I love about operator overloading is that now you can't
           | use operators without looking at their definition, in which
           | case.. you could have done numpy.equals(a, b) anyway.
           | 
           | Does a == b true, if all elements are the same? Does it
           | return an array of booleans? It's anyone's guess!
        
             | tzs wrote:
             | The function call approach can be a lot less readable.
             | 
             | Consider using Shamir secret sharing to share a secret, D,
             | among several people with two people required to recover
             | the secret. D is a positive integer, such as a randomly
             | generated 128 bit AES key you are using to encrypt your
             | launch codes or credit card database.
             | 
             | For anyone not familiar with Shamir secret sharing what you
             | do is pick a prime number, p, that is larger than D and
             | another random positive integer A, that is less than p.
             | Then give each person a pair of numbers, (i, (Ai + D) % p),
             | where each person gets a different i (which should be a
             | positive integer less than p...it is OK to simply use 1, 2,
             | 3, ...). Let's let Di = (Ai + D) % p.
             | 
             | (This is for the case where you want any two people to be
             | able to launch your missiles or decrypt your database. If
             | you wanted 3 required instead of giving out (i, (Ai + D) %
             | p) you would give out (i, (Bi^2 + Ai + D) % p) where B is a
             | randomly chosen positive integer less than p. For 4
             | required add on a Ci^3 term, and so on).
             | 
             | Given (i, Di) and (j, Dj) and p it is possible to recover A
             | and D.
             | 
             | Here's what that looks like in a language where the big int
             | library uses an accumulator style, i.e., operations are of
             | the form X = X op Y, where the ops are methods on the big
             | int objects. Assume Bi and Bj are big int objects
             | initialized from i and j, and Di and Dj are already big
             | into objects, as is p. This particular example is using
             | Perl. (This is very old code. Since 2002 you can add a "use
             | bigint" pragma to Perl code and then it would look a lot
             | more like the second Python example below).
             | my $A = $Dj->copy()->bsub($Di);  # Dj-Di
             | $Di->bmul($Bj);                  # j*Di
             | $Dj->bmul($Bi);                  # i*Dj
             | $Di->bsub($Dj);                  # j*Di-i*Dj
             | $Bj->bsub($Bi);                  # j-i
             | $Bj->bmodinv($p);                # (j-i)'
             | $Di->bmul($Bj);                  # (j*Di-i*Dj)*(j-i)'
             | $Di->bmod($p);                   # (j*Di-i*Dj)*(j-i)'  mod
             | p       $A->bmul($Bj);                   # (Dj-Di)*(j-i)'
             | $A->bmod($P);                    # (Dj-Di)*(j-i)'  mod p
             | 
             | At this point, the recovered A is in $A and the recovered D
             | is in $Di
             | 
             | Here's what it looks like in a language with the ops as
             | function calls taking the big int objects as arguments.
             | This example is Python without using operator overloading.
             | import operator as op       def recover(i, j, Di, Dj, p):
             | j_i_inv = pow(op.sub(j, i), -1, p)         A =
             | op.mod(op.mul(op.sub(Dj, Di), j_i_inv), p)         D =
             | op.mod(op.mul(op.sub(mul(j, Di), op.mul(i, Dj)), j_i_inv),
             | p)         return A, D
             | 
             | Probably more readable than accumulator style. Here it is
             | in Python using its built-in operator overloading for big
             | ints:                 def recover(i, j, Di, Dj, p):
             | j_i_inv = pow(j-i, -1, p)         A = ((Dj - Di) * j-i_inv
             | ) % p         D = ((j*Di - i*Dj) * j_i_inv) % p
             | return A, D
             | 
             | I'd sure rather come across that than either of the earlier
             | examples.
             | 
             | OT: this reminds me of something I started to do once but
             | never finished. I was going to write for each language we
             | used at work that had a big int library but that did not
             | support operator overloading a class that implemented a big
             | int RPN calculator. Java, for example. Then recover would
             | look something like this:                   calc = new
             | BigRPNCalc();         calc.do(j, i, "-", p, "modinv dup");
             | calc.do(Dj, Di, "- *", p, "mod swap");         calc.do(j,
             | Di, "*", i, Dj. "* - *", p, "mod");         D = calc.pop();
             | A = calc.pop();
             | 
             | But I never ended up needing big ints in any of those
             | languages so never really got past some initial design
             | work.
        
             | NewJazz wrote:
             | What's fun is that it could return an array of false if all
             | elements are different, and then that value is truthy.
        
         | vitamark wrote:
         | There are examples like ORM query builders (something like
         | `User.id == user_id` should not return a boolean, but rather
         | some inspectable query part), multi-value comparisons (e.g.
         | numpy arrays and views which could also be used as masks for
         | indexing)
         | 
         | In general, when you get your hands on operator overloading you
         | get a bunch of various quirky applications for each. Some
         | dunder methods have strict runtime-level rules (e.g. __hash__
         | or __len__), some don't
        
           | brandnewideas wrote:
           | That's one of the things I truly didn't get from my (very
           | limited) experience with SQLAlchemy. Why not just have a
           | method like orm.eq(User.id, user_id)? Much more readable.
        
         | olooney wrote:
         | It could return a vector or a deferred expression? In polars,
         | for example, operations on `pl.col` return `Expr` objects that
         | are used to build queries, not immediately evaluated:
         | df.filter(pl.col("status") == "active")
         | 
         | In numpy, `x == y` return a boolean vector of the same shape as
         | x and y, comparing them element-wise.
        
         | throwaway894345 wrote:
         | IIRC, SQLAlchemy overloads this to return an object that
         | represents an equality check in SQL. Because it was returning
         | an object, it was always evaluating to True, because of another
         | of Python's footguns: truthiness/falsiness. This was a decade
         | ago, and these particular footguns were not even remotely the
         | biggest culprits in our bug backlogs (another honorable mention
         | includes accidentally calling a sync function in an async
         | context, causing timeouts in unrelated endpoints and leading to
         | cascading system failure).
        
         | kuschku wrote:
         | Primarily, because Python doesn't have quasi-quoting. You can't
         | pass an expression without workarounds like this.
        
       | dsign wrote:
       | If you are going to be super-strict with type-checking, wouldn't
       | it be best to switch to a statically typed language and get the
       | performance gains as well?
        
         | ocamoss wrote:
         | Running more type checkers isn't really about strictness. The
         | main benefit to library maintainers is to make sure that their
         | APIs are compatible with whatever tools their users run.
         | 
         | This wouldn't really be an issue for most other languages, but
         | Python's typing ecosystem is uniquely fragmented, with only
         | partial standardization between several popular tools.
        
           | fg137 wrote:
           | Hmm... that doesn't answer the question?
        
             | locknitpicker wrote:
             | > Hmm... that doesn't answer the question?
             | 
             | GP's point is obvious: performance is immaterial to the
             | discussion. Static code analysis is about preventing bugs.
             | Therefore OP fails to make any sort of point, as it's a
             | straw man argument.
        
         | pmontra wrote:
         | Hallelujah, that's always been my position. To the static
         | typing folks: leave my dynamically typed languages alone and go
         | coding with something that really suit your needs. If the
         | answer is that Python, Ruby, JS, whatever are really much more
         | pleasant to code with, my reply is that they are so precisely
         | because we don't have to type type definitions. Tradeoffs.
        
           | Tade0 wrote:
           | Personally I like having my TypeScript cake and eating it.
           | 
           | I also truly believe those who design type systems would
           | benefit from taking a look what kind of code people
           | programming in dynamically-typed languages produce.
        
             | anamexis wrote:
             | I do too, but I feel like TypeScript stands alone as an
             | unusually effective and pleasant to use bolted-on type
             | system. I've not seen any other approach come close. (My
             | sample size is Python, Ruby and Elixir)
        
               | dnautics wrote:
               | you don't think the elixir type system is effective? I've
               | never seen a bolted-on type system get so much acceptance
               | from the hardcore "you can add types into my dead hands"
               | crowd
        
               | jamesfinlayson wrote:
               | I really like PHP's type hints (I think they were the
               | first I used) though it's somewhat limited (can't type
               | hint complex/nested structures last time I checked).
               | 
               | Flow for Javascript was okay but Typescript I've found to
               | be much nicer (last used flow years ago but occasionally
               | I'd encounter bugs in Flow).
               | 
               | Python's is okay but it feels clunky.
        
             | junon wrote:
             | I hate TS's tooling with a burning, deep passion. But its
             | type system is actually pretty incredible for what it is.
             | 
             | There are times that I yearn for TS's ability to do duck
             | type reasoning in e.g. Rust (despite that not being
             | feasible) when working with very large data types.
        
             | joha4270 wrote:
             | Could you point me towards the kind of code people
             | programming in dynamically-typed languages produce?
             | 
             | I have lived in statically typed languages almost all of my
             | life, and even when I don't, I pretend I do, just without
             | having a typechecker. So I'm very curious about what I'm
             | missing.
        
               | pmontra wrote:
               | Any Rails app. A random one: Redmine. You can look at
               | this file and browse the rest of the repository.
               | 
               | https://github.com/redmine/redmine/blob/master/app/contro
               | lle...
        
             | throwaway2037 wrote:
             | Does Python needs its own TypeScript moment? Many times,
             | while writing Python and deeply frustrated with its
             | weak(er) type system, I have dreamed of something like
             | TypeScript or VB/VBA from the early 2000s (where the type
             | system was surprisingly strict!). However, there are so
             | many Python libraries written in pure C, it is way harder
             | to create a TypeScript equivalent.
        
             | zokier wrote:
             | I find it funnysad that python people coined the phrase
             | duck typing and then ended up designing what they have now.
             | Meanwhile TS manages to embody duck typing far better even
             | though coming from very different background.
        
           | cogman10 wrote:
           | It's not an all or nothing thing.
           | 
           | I think types are particularly valuable for libraries. A
           | library author using copious types really helps the
           | downstream user to know "Ok, this function returns a
           | dict(Foo, Bar)". But after that, it's a matter of preference
           | if you want to add those types to your own code or not.
           | 
           | Having the types in the libraries makes it a lot easier for
           | your tools/IDEs to give good suggestions and catch bugs that
           | you might otherwise miss.
        
             | eloisius wrote:
             | Yes, where would I be without the
             | _RelationshipBackPopulatesArgument type of
             | sqlalchemy.orm.relationship(argument:
             | _RelationshipArgumentType[Any] | None = None, secondary:
             | _RelationshipSecondaryArgument | None = None, *, uselist:
             | bool | None = None, collection_class: Type[Collection[Any]]
             | | Callable[[], Collection[Any]] | None = None, primaryjoin:
             | _RelationshipJoinConditionArgument | None = None,
             | secondaryjoin: _RelationshipJoinConditionArgument | None =
             | None, back_populates: _RelationshipBackPopulatesArgument |
             | None = None, order_by: _ORMOrderByArgument = False,
             | backref: ORMBackrefArgument | None = None, overlaps: str |
             | None = None, post_update: bool = False, cascade: str =
             | 'save-update, merge', viewonly: bool = False, init: _NoArg
             | | bool = _NoArg.NO_ARG, repr: _NoArg | bool =
             | _NoArg.NO_ARG, default: _NoArg | _T = _NoArg.NO_ARG,
             | default_factory: _NoArg | Callable[[], _T] = _NoArg.NO_ARG,
             | compare: _NoArg | bool = _NoArg.NO_ARG, kw_only: _NoArg |
             | bool = _NoArg.NO_ARG, hash: _NoArg | bool | None =
             | _NoArg.NO_ARG, lazy: _LazyLoadArgumentType = 'select',
             | passive_deletes: Literal['all'] | bool = False,
             | passive_updates: bool = True, active_history: bool = False,
             | enable_typechecks: bool = True, foreign_keys:
             | _ORMColCollectionArgument | None = None, remote_side:
             | _ORMColCollectionArgument | None = None, join_depth: int |
             | None = None, comparator_factory:
             | Type[RelationshipProperty.Comparator[Any]] | None = None,
             | single_parent: bool = False, innerjoin: bool = False,
             | distinct_target_key: bool | None = None, load_on_pending:
             | bool = False, query_class: Type[Query[Any]] | None = None,
             | info: _InfoType | None = None, omit_join: Literal[None,
             | False] = None, sync_backref: bool | None = None,
             | dataclass_metadata: _NoArg | Mapping[Any, Any] | None =
             | _NoArg.NO_ARG, \*kw: Any) - _RelationshipDeclared[Any]*
        
               | cogman10 wrote:
               | It's not for you, it's for your IDE. And if you aren't
               | using an IDE then you can pretty much ignore it anyways.
               | 
               | You are in exactly the same position as if you knew or
               | didn't know that type.
        
               | nonethewiser wrote:
               | If you're not using and IDE nor an LLM
        
               | chuckadams wrote:
               | Part of it is due to the clunky `_NoArg.NO_ARG` business
               | for optional params. Pretty-printing it would also go a
               | long way, but that technology seems too advanced for any
               | language circa 2026.
        
               | tlavoie wrote:
               | It's probably hard to come up with something messier than
               | SqlAlchemy here. Not an expert, but spent more than
               | enough time spelunking queries in the debugger. I much
               | prefer bugs that can be surfaced at compile-time rather
               | than run-time.
        
               | strbean wrote:
               | _RelationshipBackPopulatesArgument = Union[ str,
               | PropComparator[Any], Callable[[], Union[str,
               | PropComparator[Any]]], ]
        
               | Ardren wrote:
               | What function signature isn't going to look messy with 36
               | keyword arguments.
               | 
               | https://github.com/sqlalchemy/sqlalchemy/blob/0798e6cbe11
               | b30...
        
               | AdieuToLogic wrote:
               | >> I think types are particularly valuable for libraries.
               | 
               | > Yes, where would I be without the
               | _RelationshipBackPopulatesArgument type of ...
               | 
               | (proceeds to list a signature with over 40 parameters)
               | 
               | You would be left wondering which of the 40+ arguments
               | provided to a given invocation is not what was allowed
               | _without_ a compiler to tell you.
               | 
               | Have fun tracking down which one, or ones, is causing the
               | problem.
        
             | shevy-java wrote:
             | This is even worse because you attempt to try to sell why
             | types SOMETIMES make sense. But you aim with this for a
             | language that did not have nor need types to begin with.
             | People don't seem to understand that this is an issue.
             | 
             | The library-situation is really not different from having
             | types everywhere, and some people will do that too.
             | 
             | > catch bugs that you might otherwise miss.
             | 
             | People repeat this a lot. In about 22 years of writing ruby
             | code, I have never ran into a situation once where I would
             | have caught a bug through types. I don't understand why
             | people keep on repeating this. Repetition does not make it
             | anymore true.
             | 
             | Think in the opposite way: if types would have been
             | necessary to begin with, why would ruby have been
             | successful back in 2006? It was successful without types
             | already. And types were never needed - they came because
             | some people THINK they are needed. This is the biggest
             | problem - the thinking part. They think they are right and
             | all who do not use types, must be wrong and very foolish
             | people.
        
               | dminik wrote:
               | Have you considered these people in general aren't some
               | outsiders out to attack you or your favorite language?
               | 
               | The people who do end up making and using type checkers
               | are people who have or are actively using these dynamic
               | languages and found out that they CAN help THEM with
               | preventing bugs.
               | 
               | Also, really? 22 years in which not one type-related
               | error happened? Never? I don't want to say I don't
               | believe you, but I really don't.
        
               | stackskipton wrote:
               | SRE here, I've had multiple outages caused by lack of
               | typing in both Ruby and Python where bad types get
               | passed, something doesn't catch it and either data
               | corruption or constant crashes. Couple cost us big money
               | because it screwed up billing and we were forced to eat
               | the billing cost.
        
               | 12_throw_away wrote:
               | > In about 22 years of writing ruby code, I have never
               | ran into a situation once where I would have caught a bug
               | through types.
               | 
               | In 22 years you have never seen `nil` show up in places
               | it wasn't expected? Really?
        
               | nijave wrote:
               | >In about 22 years of writing ruby code, I have never ran
               | into a situation once where I would have caught a bug
               | through types.
               | 
               | I've definitely ran into that although much less common
               | at places with good test discipline.
               | 
               | I think the related and often conflated problem is errors
               | caught by compilers which you don't hit til runtime in
               | Ruby/Python without good test coverage. For example,
               | referencing an undefined variable
        
               | madeofpalk wrote:
               | > _In about 22 years of writing ruby code, I have never
               | ran into a situation once where I would have caught a bug
               | through types_
               | 
               | You must be the world's greatest programmer with perfect
               | memory. Every nil pointer exception is a bug a (good)
               | type checker could have caught. You've _never_ had a
               | NameError or NoMethodError in Ruby?
        
               | saxenaabhi wrote:
               | Your app didn't silently break when you upgrade rails or
               | any other gem?
               | 
               | If ruby was statically typed the typechecker would have
               | caught it.
        
               | rowanG077 wrote:
               | This is perhaps the least believable comment I have seen
               | on HN, ever. It would be more believable for someone
               | using C to say "In about 22 years of writing C code. I
               | have never ran into a memory bug".
        
             | tasuki wrote:
             | > It's not an all or nothing thing.
             | 
             | It kind of is? All the partial-typing systems are too
             | complex and usually broken in various ways. Compare to eg
             | Elm or Gleam which are typed and super simple.
        
               | cogman10 wrote:
               | It kind of isn't. We are talking about using types in
               | type optional languages. We aren't talking about the
               | quality of those type systems or whether or not they are
               | good type systems.
               | 
               | If I was comparing type systems then it'd be relevant to
               | talk about statically typed languages like Elm or Gleam.
        
           | shevy-java wrote:
           | I am totally with you and I am glad I am not the only one who
           | is totally against those type-addictions leaking into
           | languages that did not need them in the first place.
           | 
           | Types in ruby are even worse than in python, because the type
           | systems in use really make ruby turn very ugly. In python it
           | is not as much as a huge problem with regards to syntax, as
           | python has a stricter syntax (e. g. mandating foo.bar()
           | whereas in ruby you can typically omit the (), among other
           | syntax sugar examples).
           | 
           | We need to keep the type people out of those languages.
           | 
           | Many years ago, on IRC, on #haskell, they said they don't
           | want everyone to use Haskell. Back then I did not understand
           | it. After the type-addicted people emerged out of nowhere, I
           | now begin to understand why Haskell is so snobbish. If you
           | let every idea float, you end up ruining languages - and then
           | those who wanted this, will retire and move away too.
           | Ultimate damage factor caused as outcome here.
        
           | kibwen wrote:
           | _> To the static typing folks: leave my dynamically typed
           | languages alone_
           | 
           | Surely you understand that the push to add types to
           | dynamically-typed languages comes from dynamic-typing folks,
           | not from static-typing folks. People who are deeply into
           | static typing have little incentive to consider e.g. Python,
           | whose support for types is relatively weak, loosely-defined,
           | and rarely-enforced compared to the statically-typed
           | languages that exist today.
        
             | kh_hk wrote:
             | Doesn't it come from folks that are forced to work with
             | dynamically-typed languages but can't be arsed to
             | understand them?
        
           | Hizonner wrote:
           | The only reason I gave up resisting and started writing any
           | significant code in Python at all was that it got some kind
           | of type system, and thus became less unpleasant to code with.
           | 
           | "Pleasant to code with" does not describe getting
           | "AttributeError: 'NoneType' object has no attribute 'foo'" 25
           | levels deep in a stack trace already obfuscated by dynamic
           | object-oriented nonsense. In production, because it's an
           | unusual case and testing missed it. Not that test cases
           | aren't way more work than types anyway.
        
           | thr1owaway9621 wrote:
           | I started using types with Python in 2018-ish, and I never
           | looked back.
           | 
           | I am not that good a programmer, so maybe I am wrong, but I
           | just like being able to tell what the data is that's moving
           | through the system. Typed function signatures, a little
           | shift+k here and there, a warning that I am trying to add int
           | and a string. I don't see what's the harm in having that?
           | 
           | At the end of the day, if you don't want to use Python with
           | types -- do not. Unless somebody at work is forcing you, and
           | it feels like putting lipstick on a pig (especially with
           | something like numpy that doesn't easily support types)? Then
           | condolences.
        
             | nonethewiser wrote:
             | But why does your appreciate of type systesm not lead you
             | to something like Typescript? Which is a lot more robust?
             | Or Rust? C#?
             | 
             | I guess my speculation is that not every language is good
             | at everything. Sure you might want a better type system
             | with Rust. But for data science?
        
               | thr1owaway9621 wrote:
               | In practice, inertia is stopping me.
               | 
               | For personal projects, I don't want to learn Rust just so
               | I can do `def add(a: int, b: int) -> int`.
               | 
               | For work, I don't really get a choice. I work on
               | brownfield projects. We do use TypeScript, thankfully,
               | for all the browser bits. But nobody is going to stop to
               | refactor a 5 year old production code base from Python to
               | Go just for better types. And -- pepega -- definitely not
               | our codebase that's full of data sciency stuff
               | (numpy/opencv/pandas). So we live with a not-as-good-as-
               | it-could-have-been type system.
               | 
               | Compromises, man %) One of the constants in life.
        
               | J_Shelby_J wrote:
               | Meh, the amount of effort required to keep up to date
               | with the python ecosystem churn is around the same as
               | learning rust. More so if you are starting from scratch.
               | 
               | I quit python after realizing the amount of effort it
               | required to just implement the tooling for a project...
               | when all of that comes included with rust. I have spent
               | maybe an hour in the last year thinking about tooling.
               | Glorious.
               | 
               | But yeah, I feel for you. It is an impossible sell when
               | they pay off is impossible to understand without a Time
               | Machine and the only thing known about the cost is that
               | it's high. But for new people and projects, I can't
               | imagine starting with python in 2026.
        
               | pmontra wrote:
               | Which Python tooling? I know that uv is replacing pip but
               | all of my costumers' projects still use pip. One of them
               | installed python with asdf. I can't think about any other
               | tool we are using except Claude, but I don't think that's
               | the kind of tool we are writing about. We deploy with a
               | custom bash script resembling Ruby's Capistrano. Those
               | projects are web apps with server generated HTML.
        
           | bborud wrote:
           | What specifically makes them more pleasant? (not a rhetorical
           | question, I want to know what's important to you)
        
             | pmontra wrote:
             | Historically (80s/90s) I started using Perl because I
             | didn't have to write all those malloc and free I spent
             | years writing in C and I could perform string operations
             | much more easily. Then in the mid 90s because of that
             | wonderful CGI.pm Perl module. But the plus of all those
             | languages, and Java, was managed memory. Then in the mid
             | 2000 I learned Rails, and after Rails I learned Ruby. It
             | was like Perl but much easier to understand and again no
             | types to type. Basically what I did in Java but in a
             | fraction of the time and in a fraction of the lines of
             | code. Then a customer asked me to work with Python on a
             | Django app so I learned Python. It looks like a Ruby
             | designed by Klingons but it's OKish.
             | 
             | All those bugs I constantly read about, they don't happen
             | very often and are a good tradeoff. Maybe Rails and by
             | Django are shielding me from some bug scenarios.
        
           | throwawayffffas wrote:
           | Totally agree. I hear a lot of rust makes it hard to write
           | incorrect programs. In my experience it makes it hard to
           | write programs in general.
        
           | chocrates wrote:
           | My engineers write better code when we enforce types.
           | 
           | It's easier to do this then retrain everyone on Go and
           | rewrite all our code.
           | 
           | New stuff is often in Go now, but prototyping quickly in
           | Python and then enforcing types when we have to get it ready
           | for production has been working decently
        
           | zelphirkalt wrote:
           | So far I have been avoiding Pydantic as a huge-ass
           | dependency. Instead I am relying on standard type
           | annotations, lots of typed dicts and at service/program
           | boundaries use a jsonschema. I like being able to specify the
           | type of most functions, and get some hints, completions and
           | so on, but I don't want to _have to_ specify every darn type.
           | I also don't want to write a class for everything. Typing
           | dicts is good and usually sufficient. If I wanted to write
           | types for everything, then I could also just write Java or
           | Rust or similar.
           | 
           | Unfortunately, I think the kingdom of nouns faction has long
           | invaded the Python world and I see more and more companies
           | demanding Pydantic and similar things. They are dragging us
           | all the way to Java land, it seems.
        
         | SatvikBeri wrote:
         | What statically typed language would you suggest for machine
         | learning and large data pipelines? I don't love Python, but it
         | has by far the largest ecosystem.
        
           | srean wrote:
           | You could try Cython and Lush. An ML dialect for _ML_ would
           | have been nice, but doesn 't exist.
        
             | pdpi wrote:
             | As funny as it would be, ML isn't really a great fit for
             | ML, I don't think.
        
               | srean wrote:
               | That's true for current ML offerings.
               | 
               | However, I think an ML designed for machine learning
               | would be nice, especially if the type system is extended
               | to multidimensional arrays shapes. Pattern matching on
               | array shapes would be rather nice. Ocaml style
               | interactive mode for exploration and compiling for
               | performance would be nice too.
        
             | thr1owaway9621 wrote:
             | Cython is a niche language for writing perf-critical bits
             | inside your Python codebase. It's like C for people who
             | don't want to learn C. At least that's how I treated it,
             | when I had to write some stuff to make some numpy ops
             | faster.
             | 
             | Cython is not in any real sense a replacement for a modern
             | data/ml stack.
        
               | srean wrote:
               | True but it's really nice way to get the benefit of type
               | checking in Python.
               | 
               | Just like you, I had started using Cython for performance
               | but then realized that I can discard a bulk of type
               | errors if I used for type checking.
               | 
               | The other benefit is that the Python library ecosystem
               | stays available.
        
           | locknitpicker wrote:
           | > What statically typed language would you suggest for
           | machine learning and large data pipelines? I don't love
           | Python, but it has by far the largest ecosystem.
           | 
           | Pay no attention to OP. It's nonsensical to even suggest you
           | should migrate away from a whole tech stack just because you
           | want to run static code analysis, specially when the argument
           | is based on having too many static analysis tools to chose
           | from. Utter nonsense.
        
           | bborud wrote:
           | Well, that's the curse of machine learning: since everyone
           | uses Python you have to deal with Python. Even though Python
           | isn't very nice when things start to get serious and you
           | don't want to spend your time fiddling with noise just to
           | make something work at scale.
           | 
           | I'd wish the ML/AI/LLM crowd would see that it is in their
           | interest to get better developer ergonomics at scale. (I
           | don't want to have to turn to C++)
        
             | nijave wrote:
             | Anything performance sensitive ends up being an extension
             | in compiled code anyway
             | 
             | Python is mostly just glue
        
               | hocuspocus wrote:
               | And now with LLMs, writing glue in Rust is cheap.
               | 
               | The ML/AI ecosystem is a minefield, and pure Rust
               | rewrites (Candle, Burn, ...) are still immature and
               | incomplete. But I'm pretty sure we're eventually going to
               | see the same uptake that's already happening in the data
               | processing world.
        
           | mountainriver wrote:
           | ML is basically the one use case for Python anymore.
           | 
           | And that even shrinks by the day
        
             | sideway wrote:
             | How so?
        
               | mountainriver wrote:
               | With writing code in english now, why have it use a slow
               | weak language?
               | 
               | ML still has a depth of libraries that can't be
               | replicated easily but ML work is decreasing by the day
               | with LLMs.
        
               | nijave wrote:
               | Because the English to code translation step is fallible
        
               | hocuspocus wrote:
               | Which is precisely a reason for not using Python, despite
               | LLMs being good at it.
        
               | hocuspocus wrote:
               | LLMs are leveling the developer experience and
               | productivity in a way that makes Python's strengths
               | almost irrelevant, while it's still suffering from bad
               | tooling (even with uv and friends) and poor performance.
               | 
               | AI/ML: interfacing with C++ libraries directly (or in
               | Rust) is now a real option. For everything else, even 5
               | years ago I wouldn't have used Python, now there are even
               | fewer reasons to do so. As far as I'm concerned the
               | remaining use cases are notebooks and one-shot scripts.
        
           | throwaway2037 wrote:
           | It is brutal. I can say with first hand experience: The APIs
           | for Pandas and NumPy are awful and insanely dynamic. As a
           | result, it is frequently difficult to know what is allowed
           | with calling a method. It is exhausting. Since many methods
           | are "hyper-dynamic", many of the error messages are
           | unhelpful.
        
         | Qem wrote:
         | > If you are going to be super-strict with type-checking,
         | wouldn't it be best to switch to a statically typed language
         | and get the performance gains as well?
         | 
         | You can use type-checking to get better performance already,
         | without leaving Python. See https://blog.glyph.im/2022/04/you-
         | should-compile-your-python...
        
         | ethagnawl wrote:
         | Yeah, I can't say I really get the appeal of gradual typing.
         | It's commented/documented code at best and outright lies at
         | worst. Sure, you can build tooling around it and improve your
         | DX a bit but isn't it always a house of cards?
        
           | ocamoss wrote:
           | > commented/documented code at best
           | 
           | Machine-checked documentation is always valuable, IMO
        
           | falcojr wrote:
           | If you add one of these type checkers into your CI or a pre-
           | commit hook, it provides the same guarantees you get from a
           | compiler along with the same tooling benefits. It gives you
           | the option of using the structure when you need it, but not
           | being forced to use it when you want to take advantage of
           | some of the more dynamic features of the language.
        
             | chuckadams wrote:
             | But none of the runtime benefits of having static types in
             | the compiler, since the runtime still can't trust the
             | types. Still, half a loaf is better than none.
        
         | MeetingsBrowser wrote:
         | strict type checking is an incredibly useful tool for cases
         | when you really want to make sure your code is correct and
         | behaving as expected (one of many tools).
         | 
         | There are lots of people who like python and want to use it for
         | things that where incorrect code has serious consequences. Type
         | checking is helpful in these contexts.
         | 
         | Type checking remains optional for the masses and is not
         | practical in many cases. Still, pushing away people who want to
         | use all available tools for writing correct python only hurts
         | the community.
        
         | locknitpicker wrote:
         | > If you are going to be super-strict with type-checking,
         | wouldn't it be best to switch to a statically typed language
         | and get the performance gains as well?
         | 
         | I don't understand your question. The whole point of static
         | code analysis is preventing bugs. Don't you like Python code to
         | not have bugs that are easily caught with static code analysis,
         | or is preventing code a foreign idea that is better left to
         | other languages?
        
           | necovek wrote:
           | I don't understand your question. Are you saying static code
           | analysis is impossible without type declarations? None of it?
        
         | IshKebab wrote:
         | Yes. _If you have a choice._
         | 
         | For people who don't have a choice, type checked Python is
         | better than nothing.
        
         | vadansky wrote:
         | Personally because I'm making a blender add on that only uses
         | python, and it's at the complexity where having types catches a
         | ton of bugs easily.
        
         | Hizonner wrote:
         | Yes, but unfortunately Python has invaded everything, and one
         | must adapt.
         | 
         | Python is going to be preinstalled on almost any machine I use,
         | with a reasonable assortment of libraries. And even if they're
         | not preinstalled, the libraries I want are likely to _exist_.
         | They 'll have unstable APIs and weird quirks, and I'll have to
         | take my choice of bad packaging systems to install them, and
         | everything will just generally be a pain, but they'll exist and
         | largely work. That's not true for any language I actually
         | _want_ to code in. I mean, I 'm not going to deny that Python
         | is better than shell scripts or (usually) C.
         | 
         | It's not like it's a pleasant language to code in, especially
         | if you actually want to _use_ the type support, which is weird
         | and irregular and keeps changing and has to work around
         | fundamental design problems at the core of the language.
        
         | scuff3d wrote:
         | Seriously, just switch to Go or something
        
         | bborud wrote:
         | Python has other, bigger problems that make it a constant
         | headache. One of them being the dismissive attitude towards any
         | and all of problems that come from versioning, dependencies and
         | quirks that make it challenging to have robustness.
         | 
         | Criticisms are typically dismissed by suggesting heaping yet
         | another "solution" onto the growing pile of "solutions" that
         | you have to drag around with you. That people have to learn.
         | That you have to install tooling for. That has to be vetted.
         | That has to become part of the toolbox to get even seemingly
         | simple things done. This attitude is a big part of the reason
         | that I strongly advise people against using Python in
         | production. On top of all the problems presented in a real-
         | world setting.
         | 
         | Almost all of the time, people who are fond of Python are more
         | interested in defending python, disparaging me, downvoting me
         | etc that listen to why I make that recommendation.
         | 
         | (I get it. People like Python. What I think of Python as a
         | _language_ is irrelevant. In fact I don 't have that much
         | against it. But I do have a lot against it in a setting where
         | you need reliability and repeatability)
         | 
         | I have spent the last month of my life building a system that
         | can run Python tooling reliably in a business critical
         | application. I knew this was going to be a pretty big job when
         | I started, but for every problem I solve, a bunch of new
         | problems arise. I am starting to see light at the end of the
         | tunnel but it hasn't exactly been smooth sailing. I'm almost
         | there for a first version, but there are a bunch of problems
         | still to solve. Mostly because I care about developer
         | ergonomics and that things should "just work". One important
         | goal is that my solution shouldn't impose any significant
         | cognitive burden on people who use it. That's really hard.
         | 
         | (I don't think the solution will be open source since my
         | contract wouldn't allow for it. But I'll make the case at some
         | point for why it should be open sourced)
         | 
         | And yes. There are statically typed languages available today
         | that have decent tooling that provides superior developer
         | ergonomics. I can understand that people don't want to learn
         | new languages, but if you have the capacity to do so I would
         | recommend trying to move on if the code you write has to run
         | outside your own workstation. If an old fart like me can learn
         | and adopt new languages, so can you.
        
           | chuckadams wrote:
           | Python the language is pretty nice. It has its warts, but I
           | make my living in PHP which is practically _made_ of warts.
           | But the python ecosystem still seems to be trying to figure
           | out this whole package management and project setup thing. In
           | most languages I can do some form of `$blub install` where
           | $blub is the language 's official package manager or some
           | close equivalent. It's just python that always screams at me
           | that I have to set up and "enter" a virtualenv. I get what
           | venv is for, but it's still a weird hack of hardlinks and
           | relative paths that no other language seems to need, and a
           | clumsy two-step dance of a UX that hasn't improved in like 20
           | years.
        
             | necovek wrote:
             | I am not sure you get what virtualenvs are: Python is never
             | screaming at you to set up a virtualenv, it must be a
             | particular package recommending use of virtualenv for easy
             | set up without interfering with the rest of your system.
             | 
             | Virtualenv allows you to seamlessly run multiple Python
             | ecosystems simultaneously, even within the same project
             | directory. It's basically primitive containerisation
             | mechanism that predates any actual containerisation systems
             | on Linux.
             | 
             | You do not have to use it, but then you can easily slip
             | into a sort of "DLL hell" (multiple incompatible library
             | versions installed system-wide) with multiple projects --
             | or need to bundle all dependencies within your project
             | directly. None of this is specific to Python, really -- any
             | shared library system has the same challenge. How many
             | other systems are there in active use making it as easy to
             | use multiple incompatible versions of shared libraries per
             | project or within the same project?
             | 
             | When in doubt, you can always retreat to the basics in
             | Python world: put packages you need in a path of your
             | choice, and point PYTHONPATH (sys.path) at it.
        
           | nijave wrote:
           | What are the problems you have with tooling? Imo it's no
           | worse than most other languages besides a very small handful
           | of recent ones (rust, go) where everything is included
           | 
           | The easy approach is usually just throw it in an OCI
           | container
           | 
           | There's not much concrete to go on here besides "I don't like
           | the ergonomics"
        
         | UltraSane wrote:
         | That is why I'm using C# and Rust more now than Python. You get
         | far better RoI on types. and they are so much faster and can
         | use all cores so much more easily.
        
         | thrance wrote:
         | Often, when I code in Python, it's because there are some
         | libraries that aren't available in whatever other language
         | would have been my first pick. Then, typing and type-checking
         | are useful tools to stave off the codebase turning into the
         | unruly mess that all Python projects eventually become.
        
           | necovek wrote:
           | Don't projects in all languages turn into unruly mess by
           | default?
           | 
           | It requires special care to not let long-running projects
           | evolve into it.
           | 
           | Python is only special in that it is extremely productive and
           | allows lots of easy evolutions of a project with not a care
           | in the world, so the timeline is probably shorter on getting
           | to the "unruly mess" if no special care is put in to make it
           | survive many evolutions.
           | 
           | Perhaps a bit special too in that it looks welcoming to the
           | masses who have no idea how software systems evolve and thus
           | do not even know there are some special patterns to introduce
           | for code to survive many changes in the future. I am
           | undecided if this is a pro or a con of the language itself.
        
         | marcosdumay wrote:
         | The goal is to be strict, with explicit exceptions.
         | 
         | You don't use a static language because you want the
         | exceptions, but the type checking can still statically validate
         | most of your code.
        
         | tptacek wrote:
         | No? One has nothing to do with the other.
         | 
         | I think those of us who work in compiled languages are just
         | snooty about them.
         | 
         | I'm a compiled language snoot, and happen to be working over
         | the past couple days in typed Python for the first time. It's
         | kind of nice. I like it. It's a huge improvement for me over
         | ordinary Python/Ruby/Javascript; it materially improves the
         | experience of working in the language.
        
           | tasuki wrote:
           | > happen to be working over the past couple days in typed
           | Python for the first time. It's kind of nice. I like it.
           | 
           | I like me a good type system and have always hated about
           | everything about types in Python. What do you find nice and
           | like about it?
           | 
           | (My experience with Python: all the type checkers are broken,
           | there are false positives _and_ false negatives everywhere.
           | The LSPs are likewise broken, I have not found one that knew
           | the types at least somewhat reliably...)
        
             | tptacek wrote:
             | Lack of typing is my biggest problem with Python, Ruby, and
             | ES6 Javascript; I have to write everything twice, once to
             | do the stuff I want, and once to double check that it's
             | actually doing stuff, because a single typo blow the
             | program up despite it parsing fine.
             | 
             | Python typing is easy to dip in and out of. It handles None
             | nicely; not as nicely as a true Optional, but enough for
             | daily driving. The annotations are readable and simple.
             | What more could I ask for, without asking for an entirely
             | different language? Python typing catches a lot of bugs I'd
             | otherwise have to tediously unit-test for.
             | 
             | The only thing I don't like about it is that it feels like
             | it relies a lot on importing stuff from the swamp of the
             | Python stdlib.
        
               | necovek wrote:
               | I believe you are right to point out how you _feel_ about
               | some of these features of a particular language, and to
               | let that guide your decision in how and whether you use
               | them.
               | 
               | It does not, however, say anything about the actual
               | productivity gain or loss of using types in a language
               | like Python which does not require them -- that should be
               | the ultimate _objective_ measure of whether they make
               | sense or not.
               | 
               | With most languages, I get annoyed if I need to create
               | separate types for every variant of a basic type (eg.
               | let's have a firstNameString, familyNameString,
               | CountryCodeString, CountryNameString... when is it too
               | much?) - I do not think there is any way someone can
               | prove going this deep improves maintainability long term.
               | Eg. imagine you introduced validation of
               | CountryCodeString based on ISO-3166, and there has been a
               | change to ISO-3166 which happen every couple of years --
               | how do you start supporting new codes? How do you
               | deprecate and remove old ones? All of those are not
               | helped with a type being very strict, you still have the
               | persistent data to worry about, code actually supporting
               | any of those, etc -- the basic type check is trivial with
               | a couple of small unit tests in comparison. You also
               | quickly venture into a territory of complex types with
               | complex interaction rules (this subvalue can be one of A
               | if another one is X; but B if another is Y).
               | 
               | So for me covering the basic invariants with a unit test
               | is not much more effort and -- especially with Python --
               | does not stop one from refactoring effectively and
               | building stable, long-running systems.
               | 
               | Really, complex relationships in data are complex, and
               | encoding it in a declarative way using a complex schema
               | does not guarantee correctness (see Pydantic); if you
               | want just very basic data conformance (type) checking,
               | it's mostly a question of ergonomics.
               | 
               | Basically, you need to keep to some principles of code
               | structure and architecture, but they are a simple set of
               | principles -- perhaps the fact that most Python projects
               | do not abide by these should be a knock against Python? I
               | only attribute it to the approachability of Python, but I
               | am open to being wrong and this being the latent forced
               | idiomatic use that projects _always_ evolve into?
        
               | tptacek wrote:
               | What I like about Python types is that they accommodate
               | both styles of programming. I happen to be completely
               | sold on at least some baseline level of type safety (I
               | don't need my type system to be a complete modeling
               | toolkit for everything in my problem domain, just enough
               | for basic sanity), but if you're an old-school Python
               | type, you and I can work on the same codebase without
               | types ruining your life.
        
         | globular-toast wrote:
         | Show us the language and we'll all switch tomorrow.
        
         | Animats wrote:
         | The modern approach seems to be to require full typing on items
         | seen from outside the function or object. Within functions,
         | have the compiler infer as much as it can. Newer languages (Go,
         | Rust) seem to be converging on this approach.
         | 
         | Function parameters need type info as guidance for people and
         | LLMs calling the function. Even though cross-function type
         | inference is technically possible, it's too confusing. Long-
         | distance inference failures tend to generate poor messages.
         | 
         | Within a function, if you have typed parameters, the type
         | inference engine has a local starting point and a good chance
         | of success on most local variables.
         | 
         | Unchecked advisory typing in Python was a terrible idea. All
         | the work of writing type declarations with none of the
         | benefits.
        
         | throwaway2037 wrote:
         | Shhh! You're not supposed to say that part out loud.
        
         | lijok wrote:
         | ML
         | 
         | Data tooling
         | 
         | Talent pool
         | 
         | Libraries for customers
         | 
         | Brownfield codebases
         | 
         | Academics
         | 
         | I can keep going...
        
         | chpatrick wrote:
         | Nothing can beat the Python numpy/ML ecosystem. There's a lot
         | of value in just being able to run a Python script as well
         | without any compilation step. The typing isn't perfect right
         | now but it's usable.
         | 
         | For vectorizable problems there also won't be huge performance
         | gains from switching to a compiled language because all the
         | hard stuff is already done in highly optimized native code. The
         | only time it really makes a difference is if you have to write
         | a custom for loop or traversal.
        
       | blahgeek wrote:
       | > Prioritise running as many type-checkers as possible on your
       | test suite. Run at least one on your source code.
       | 
       | There are two types of tests: those that test against the public
       | API, and those that test internal codes with various mocks and
       | fakes. I think the vast majority of _unit tests_ is the latter
       | one, in which case the suggestion does not really make sense.
        
       | kingstnap wrote:
       | The fact that this article seems to honestly recommend people run
       | 5 different type checkers on library test suits really reflects
       | the tacked on feeling of Python typing.
        
         | vitorsr wrote:
         | I am not sure it is _recommending_ more than it is commenting
         | on the current state of developing public-facing APIs in
         | Python.
         | 
         | The downstream users that import the package either have to
         | ignore checking its exported types altogether, manually stub
         | it, or have a subpar development experience to varying degrees.
         | 
         | This is something I saw the other day with some package that
         | provided comprehensive stubs for an untyped library. The .pyi
         | file was littered with comments about quirks from the numerous
         | type checkers (five now).
        
         | TremendousJudge wrote:
         | It's ridiculous. They should have made it an explicit part of
         | the language. The interpreter knows about types already, it's
         | crazy that they couldn't just let the user make the types
         | explicit rather than implicit, and have the interpreter enforce
         | that.
        
         | ActorNightly wrote:
         | No, it reflects the nature of misunderstanding Python by people
         | who think their system is better, have no idea how Python in
         | production actually works, and just publish things like the
         | article to make themselves feel better.
         | 
         | Typing is not a huge issue, period. In Python, if you pass a
         | wrong type to something, program just throws exceptions.
         | Exceptions are not the end of the world like people make it
         | seem. Functionally, finding errors during the process of taking
         | code and compiling it with type checking is no different than
         | taking code and just running it against a set of tests, which
         | every production code has (or should have)
         | 
         | The only waytyping ever saves you from it is by being
         | absolutely strict - every type defined has a finite range of
         | values, and every operation has bounded domain and range. I.e
         | if you have a string field, its not enough that its a string,
         | you also must define the total number of characters that string
         | can have, and values for each character, along with more
         | complex rules on sequences of characters.
         | 
         | If you have this system, (something like Coq comes close), then
         | if your program compiles, its by definition correct. But even
         | the strongest proponents of typing don't really want to do
         | this, because they realize how long it would take to write
         | code.
         | 
         | The simple truth is that Python is easy and flexible enough to
         | work in that you don't even need type checking. An LLM can
         | effectively function as a type checker for you if you care
         | enough. For any errors that you encounter due to lack of
         | typing, its ultimately way faster to fix with Python than it is
         | to spend time writing strongly typed language.
        
       | shermantanktop wrote:
       | That blog needs to run a AI checker. Content aside, a lot of the
       | writing is pure AI style.
       | 
       | > The type checking that matters most (and why you've probably
       | got it backwards)
       | 
       | Honestly, I don't care if the author got some AI help. But that
       | click-bait style is ubiquitous and obnoxious.
        
       | ghostly_s wrote:
       | Why would users care if you're using the same type checker as
       | them? Surely they're not expecting all their imports to be
       | instrumented for running redundant types checks?
        
         | Someone wrote:
         | Users do not care about that, but they want to not see type
         | errors or warnings when they integrate your API in their code.
         | 
         | That's why you want to run their type checker on your API. you
         | cannot know what "their type checker" is, so you want to run
         | all popular type checkers on your API.
        
           | ForHackernews wrote:
           | Sounds like a them-problem. Their type checker can accept my
           | declared typings for my public API, or they can override it
           | with their own custom type stubs if they have objections.
        
       | KolmogorovComp wrote:
       | Why anyone would still use mypy besides legacy infrastructure is
       | beyond me. It is dog slow as well as being the laziest of all,
       | not catching many mistakes.
       | 
       | Unfortunately for Django apps switching to any alternative leads
       | to the dreaded "wall of errors" issue. If anyone got to work this
       | out in the past, I'd gladly take advices.
        
         | thr1owaway9621 wrote:
         | I use pyright with a 50k LOC Django REST API codebase. I
         | haven't really had problems. From my pyproject.toml:
         | 
         | django==4.2.30
         | 
         | djangorestframework==3.16.1
         | 
         | ---
         | 
         | django-types==0.15.0
         | 
         | djangorestframework-types==0.8.0
         | 
         | pyright==1.1.390
         | 
         | My dj version is pretty old, but I'd assume things have only
         | gotten better since v 4?
        
       | shevy-java wrote:
       | The type-lovers will be angry! :)
       | 
       | The blog entry fits into ruby too, to some extent; while the
       | situation is nowhear near as bad as in python, you have the same
       | question-marks why types suddenly emerge out of nowhere. Almost
       | ... almost as if some people have a specific agenda, and try to
       | pull through with it.
       | 
       | Well, there you have it - the type-addicted people are ruining
       | python.
        
       | prodigycorp wrote:
       | what are ppls' impression of pyrefly? i've become completely
       | captive to uv's tooling. it has allowed me to think only about
       | coding versus tooling. dont feel like giving another typechecker
       | a chance unless it offer's something i'm not getting from ty.
        
       | zerof1l wrote:
       | From my experience with Python, both personal and professional, I
       | find it immature and not well-suited for large codebases. Typing
       | should have become part of the language a long time ago; it is
       | clear that users want it.
       | 
       | Take, for example, PHP... look at the features released in the
       | last 6 or so years, starting with PHP 7, and how mature the
       | language has become.
       | 
       | With the advance of AI-assisted programming, I feel like Python
       | is always a bad choice.
        
       | __mharrison__ wrote:
       | I'm happy w/ ty right now. My agents runs it fast and it seems to
       | provide great guardrails.
        
       | semiinfinitely wrote:
       | Everything that isn't uv, ty, ruff is wrong and deprecated
        
         | pvdebbe wrote:
         | Will you update the list for us, a month from now?
        
       | VoidWarranty wrote:
       | Dynamically typed languages are going to decline with the rise of
       | AI coding.
       | 
       | Statically typed languages provide the determinism necessary to
       | efficiently anchor probabalistic coding agents.
       | 
       | You can throw as much type checking at dynamic languages after
       | the fact, but youre just going to burn energy (and tokens) doing
       | what another language gets 'for free'.
        
         | necovek wrote:
         | I am not so sure that's this simple.
         | 
         | No matter your preference, programs in dynamically typed
         | languages are still very much deterministic.
         | 
         | To be able to reason about the output of LLMs (though it is
         | debatable how often will this be needed), you want the output
         | from your imprecise human language spec to a deterministic spec
         | (code) to be as easy to review as possible (for correctness,
         | but mostly for any glaring errors). With proper setup, ensuring
         | correctness of one deterministic output (Python) in comparison
         | with another (eg. typed language like Rust) is just a
         | deterministic run away (a test suite) that should not use any
         | tokens from the LLM, and should have no practical differences
         | in compute use.
        
           | fasterik wrote:
           | Static type checking catches a whole class of programming
           | errors for free. Writing tests costs tokens or human time, so
           | you end up needing more code (and probably more CPU time) to
           | achieve the same level of error-checking in a dynamic
           | language.
        
             | necovek wrote:
             | Very simplistic look, IMO. I'll add another one mostly as a
             | counterpoint (not that I believe it is strictly true, but
             | largely yes!).
             | 
             | Python is a lot more expressive than other languages and
             | has a very terse syntax, and thus requires LLM to output
             | much fewer tokens to achieve the same job compared to other
             | languages.
             | 
             | Adding a few more tests to ensure data conformity on top of
             | what you have to do anyway with a statically typed language
             | still results in fewer tokens overall.
        
               | Smaug123 wrote:
               | _Does_ it have a terse syntax? I main F#, and when I have
               | to work with Python I generally find myself complaining
               | about how verbose it is. (Needing intermediate variables
               | for what should have been a pipeline, the ceremony around
               | parallelism, having to store constructor parameters as
               | object fields, etc.)
        
               | necovek wrote:
               | It sure does in comparison with most mainstream
               | statically typed languages -- if you feel that way about
               | Python, I wonder what you say about Java, C++ or even
               | Rust or Go?
               | 
               | Checking some examples at https://learn.microsoft.com/en-
               | us/dotnet/fsharp/tour, I'd say it's quite similar in
               | verbosity -- eg. no need to declare a module in Python
               | since the code already lives in a file that is the module
               | (plus one less indentation level for module level
               | functions); inline function declaration and calling is
               | thereabouts with F# slightly more terse (let vs lambda +
               | spaces vs parenthesis); if-then more verbose in F# (no
               | then in Python, just "if x:"); F# does not seem to need
               | "return"...
               | 
               | In many cases you can avoid intermediate varibles: inline
               | ifs, list comprehensions, lambdas, etc... Constructor
               | arguments are a good point, but this is mostly about
               | idiomatic use instead of language itself: you can simply
               | do                 def __init__(self, **kwargs):
               | self.args = kwargs
               | 
               | I'll give you one on the ceremony around concurrency,
               | though! I have different ideas of how it should have been
               | done to shift the cost to the language runtime instead of
               | the developer, but alas... :)
        
               | fasterik wrote:
               | The expressiveness of a language and whether it does
               | static/dynamic type checking are orthogonal. Type
               | inference and generics are a thing in most modern
               | languages. I also think you're underestimating the amount
               | of code you need to guarantee that there are no type
               | errors in your Python code.
        
         | AsthmaBoy wrote:
         | Python is dynamically, strongly typed. It cares a lot about the
         | types of its objects, you can't just mix and match at will.
         | 
         | Perhaps you meant statically typed?
        
           | VoidWarranty wrote:
           | Oops thank you, I did mean static. I should know better as I
           | am a cpp fan. Updated.
        
         | ActorNightly wrote:
         | This is probably the most intellectualism ive seen anyone put
         | into a comment that is so very, very, obviously wrong.
         | 
         | Yeah, in the age of AI where the whole goal is to not have to
         | think, type as fast as you can with misspellings, and copy
         | paste stuff without thinking, its TOTALLY a better system to
         | worry about the types of whatever you are feeding into llms.
        
           | VoidWarranty wrote:
           | Its about limiting surface area.
           | 
           | The gymnastics people are putting their ops teams through in
           | order to validate oceans of generated slop is insane. Just
           | use Rust and half of that work goes away.
        
       | leni536 wrote:
       | This is what the typing spec says about type narrowing[1]:
       | 
       |  _" Type checkers should narrow the types of expressions in
       | certain contexts. This behavior is currently largely
       | unspecified."_
       | 
       | Have fun.
       | 
       | [1]
       | https://typing.python.org/en/latest/spec/narrowing.html#type...
        
       | braiamp wrote:
       | What is this saying differently from
       | https://peps.python.org/pep-0827/ ?
        
         | ocamoss wrote:
         | Everything? The blog post and PEP have almost nothing to do
         | with each other lol
        
       | throwawayffffas wrote:
       | The whole type checking experience in python has disappointed me
       | deeply and is seriously affecting my work.
       | 
       | I see the appeal for type-checking and yeah it has caught many
       | bugs. But the language is quickly running blindly to the worst of
       | all worlds in regards to typing.
       | 
       | 1. You have to exhaustively write types in many cases where they
       | can be obviously inferred.
       | 
       | 2. The type checking is just a lint step. i.e. we are still
       | paying for the duck typed typing system.
       | 
       | 3. We no longer get to use the duck typed typing system making a
       | lot of generic code require obscure annotation incantations to
       | pass the lint check while it's correct python code.
       | 
       | My ideal typing system would be around constraints introduced by
       | the code and completely inferred unless the user wants to tighten
       | the constraints. i.e
       | 
       | Instead of                 def foo(a: int, b: int) -> int:
       | return a + b
       | 
       | You would write:                 def foo(a, b):         return a
       | + b
       | 
       | And upon checking if you tried to do foo(5, {})
       | 
       | It would tell you that there is no + operator for int and
       | dictionary that is required by the foo function.
       | 
       | My ideal typing system would allow you to constraint the types as
       | well like so                 def foo(a: int, b: int):
       | return a + b
       | 
       | The return type is not required in this case because it can be
       | inferred by the function definition. For other cases it could be
       | defined as well to constraint that we don't want None for
       | example.
        
       | stephbook wrote:
       | Five different type-checkers and even type-checking projects
       | think adding multiple "ignores" is sound code. Typescript would
       | allow overloads without ignores, for example.
       | 
       | Python's type checking ecosystem truly is a mess.
        
       | insane_dreamer wrote:
       | I use ty with Zed.
       | 
       | But PyCharm's built-in type checker is far and away the best that
       | I've used with proper type inference through multiple class
       | inheritance hoops.
        
       | ryanshrott wrote:
       | I run pyright in CI and mypy locally. They catch different things
       | - pyright is stricter on overloads, mypy catches more None issues
       | in our codebase. Annoying, but I have not found one that covers
       | both.
        
       ___________________________________________________________________
       (page generated 2026-06-09 04:01 UTC)