Are you expected to run five Python type-checkers now?
Posted by ocamoss 1 day ago
Comments
Comment by dsign 1 day ago
Comment by pmontra 1 day ago
Comment by cogman10 1 day ago
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.
Comment by eloisius 1 day ago
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]*Comment by cogman10 1 day ago
You are in exactly the same position as if you knew or didn't know that type.
Comment by nonethewiser 1 day ago
Comment by AdieuToLogic 1 day ago
> 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.
Comment by Ardren 1 day ago
https://github.com/sqlalchemy/sqlalchemy/blob/0798e6cbe11b30...
Comment by chuckadams 1 day ago
Comment by cik 16 hours ago
Writing queries in sql and then generating for the target language also provides a flexibility that has reduced rewrite cost. Add to this ease of organization and layoit, and I'm not going back.
Comment by tlavoie 1 day ago
Comment by strbean 1 day ago
Comment by mort96 21 hours ago
As an example, I may have a variable with types:
const something = somelibrary.getSomething();
and I can type `something.` and see methods and properties. However, if my own code doesn't use types consistently, it's so easy to lose type info. For example: const something = someWrapper(someLibrary.getSomething())
or: function doSomethingWith(something) {
...
}
doSomethingWith(someLibrary.getSomething()
or any number of other patterns which accidentally strips type info from the variable if you don't use types everywhere.I would much rather have a language where the compiler complains if some variable doesn't have a static type, than a language where I can accidentally leave something untyped. I don't understand which case I would want a variable or function to not have associated static type information.
Comment by shevy-java 1 day ago
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.
Comment by dminik 1 day ago
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.
Comment by madeofpalk 1 day ago
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?
Comment by stackskipton 1 day ago
Comment by nijave 1 day ago
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
Comment by rowanG077 1 day ago
Comment by adrian_b 13 hours ago
Avoiding "memory bugs" in C is trivial, but tedious, so too many C programmers fail to use an appropriate programming style. Nonetheless, there are some who have never encountered a "memory bug" in programs written by them.
I agree that a programming language should enforce such features, instead of counting on competent programmers.
Comment by 12_throw_away 1 day ago
In 22 years you have never seen `nil` show up in places it wasn't expected? Really?
Comment by saxenaabhi 1 day ago
If ruby was statically typed the typechecker would have caught it.
Comment by tasuki 1 day ago
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.
Comment by cogman10 1 day ago
If I was comparing type systems then it'd be relevant to talk about statically typed languages like Elm or Gleam.
Comment by Tade0 1 day ago
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.
Comment by anamexis 1 day ago
Comment by jamesfinlayson 1 day ago
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.
Comment by dnautics 1 day ago
Comment by zokier 1 day ago
Comment by throwaway2037 1 day ago
Comment by joha4270 1 day ago
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.
Comment by pmontra 1 day ago
https://github.com/redmine/redmine/blob/master/app/controlle...
Comment by junon 1 day ago
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.
Comment by Cpoll 4 hours ago
My reply is that no, that's not why they're pleasant. If that were the only criteria, we could conclude Python is only as pleasant as Forth.
Comment by chocrates 1 day ago
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
Comment by throwawayffffas 1 day ago
Comment by zelphirkalt 1 day ago
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.
Comment by strogonoff 19 hours ago
With Python I can’t see myself type-annotating everything (or bringing in pydantic anymore for that matter, it is indeed becoming a blight), but with TypeScript my process is turned on its head: I find it natural and easy to start writing with types and have everything fully typed, and I find the fact that it simply won’t compile if anything is off (compared to Python where it’s more like “one of my N type checkers/linters failed, oh well it still runs though) a useful constraint that gives peace of mind.
Comment by Hizonner 1 day ago
"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.
Comment by thr1owaway9621 1 day ago
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.
Comment by nonethewiser 1 day ago
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?
Comment by thr1owaway9621 1 day ago
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.
Comment by J_Shelby_J 1 day ago
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.
Comment by pmontra 1 day ago
Comment by kibwen 1 day ago
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.
Comment by kh_hk 1 day ago
Comment by arjvik 10 hours ago
It's really easy to understand that everything is typed as Any/Object/whatever upper bound type your statically-typed language of choice uses.
Desiring something better does not mean a lack of understanding of the status quo.
Comment by nazgul17 1 day ago
Comment by kh_hk 19 hours ago
It’s easy to write correct Ruby code, but to gain the fluency needed to
write great Ruby code, you must go beyond syntax and absorb the “Ruby way”
of thinking and problem solvingComment by wolvesechoes 22 hours ago
Except when their boss tell them to use Python, or they rely on one of Python libraries that their pet language couldn't provide via its powerful type system.
Comment by adrian_b 13 hours ago
Typing "type definitions" makes you type less, not more, because you type the definition only once, instead of writing many tests wherever values of that type are used.
In a decent programming language, one would frequently avoid the need to declare the type of a variable, whenever the type can be deduced from the value used to initialize the variable.
Having types in a language has 2 purposes, one is to enable the compiler to check at compile time or at run type that all the subsequent uses of an identifier after its first occurrence are consistent.
I cannot imagine which are the benefits for the programmers who are against this rule, i.e. who want to reuse the same identifier for multiple purposes in the same scope (N.B. reusing an identifier in the same scope has nothing to do with data types that are disjoint unions or virtual types, which can be used in any type-enforcing language, or with reusing the same identifier in different scopes).
The second purpose of data types is that when the type of a variable or parameter is known at compile-time, that allows more efficient implementations, which are especially important for aggregated data, e.g. arrays.
Again, I also do not understand why anyone would want to have inefficient data representations, to avoid the need of data types.
There has been some argument that when a language does not use data types you might avoid having to rewrite some library functions if you want to change the parameter types at invocation. However this is a problem that has been better solved for more than a half of century, by providing various means to write generic functions that can be specialized at compile-time or by using disjoint union types or virtual types, for using the same function for many different data types, while still ensuring that other data types, whose use would be erroneous, are rejected.
A language without data types saves writing effort only when the programmer omits the run-type checks for correct values, which would be needed to avoid bugs when such checks are not done automatically by the compiler.
I agree that several very popular programming languages with type checking, including C and C++, are very poor examples about how a type system should be implemented, because they require the writing of a great amount of superfluous boilerplate that is completely unnecessary (e.g. writing headers with function declarations instead of extracting automatically the interfaces of a package a.k.a. module or writing explicit type names in a lot of places where they can be deduced automatically from the context).
Such languages are strawmen in a discussion about whether a language must enforce type checking or not.
Comment by bborud 1 day ago
Comment by pmontra 1 day ago
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.
Comment by bborud 8 hours ago
And dabbling doesn't really count. It takes time to actually learn a language. Much longer than most people are willing to admit.
Comment by bitwize 21 hours ago
Comment by shevy-java 1 day ago
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.
Comment by chpatrick 1 day ago
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.
Comment by ocamoss 1 day ago
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.
Comment by fg137 1 day ago
Comment by locknitpicker 1 day ago
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.
Comment by tptacek 1 day ago
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.
Comment by tasuki 1 day ago
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...)
Comment by tptacek 1 day ago
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.
Comment by necovek 1 day ago
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?
Comment by tptacek 1 day ago
Comment by Animats 1 day ago
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.
Comment by Qem 1 day ago
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...
Comment by SatvikBeri 1 day ago
Comment by FacelessJim 15 hours ago
It’s still dynamic in nature. But you can tune how much staticity you want. The spectrum goes from Python to C in terms of staticity. And with tools like JETLS.jl maturing you get a lot of the benefits static analysis.
The data pipeline ecosystem is starting to rival that of R and Python. The fact that you can just use Julia functions while keeping the performance allows you to avoid those weird vectorization gymnastics. The ML ecosystem is also in a great state. JUMP.jl, Touring.jl, the whole SciML ecosystem, autodiffing and gpu computing are all close to best in class in terms of quality. The NN side of ML is a a bit weaker, but just for lack of developer time investment into that side of the ecosystem.
Comment by SatvikBeri 7 hours ago
Comment by throwaway2037 1 day ago
Comment by bborud 1 day ago
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++)
Comment by nijave 1 day ago
Python is mostly just glue
Comment by hocuspocus 1 day ago
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.
Comment by bborud 18 hours ago
However my experience might be a bit different since I actually have to deal with Python at scale and in a fairly dynamic environment.
I need to run hundreds of Python programs, written by dozens of programmers, over many years, that speaks to custom hardware, runs on a remote site, in a production environment that has to work and with new versions of things coming in all the time. Some of these Python programs not only link with C libraries, but run external binaries because developers didn't have time to integrate them as libraries because it takes forever to make it work on all the different os/arch combinations (easier to just run the C code in subprocesses).
This runs on three different CPU architectures (we're trying to eliminate one of them), two different operating systems and a pretty wide mix of hardware and system configurations I need to insulate Python from. Much of the hardware being custom built stuff. Because Python has a lot of exposed surface to the OS compared to a statically linked binary. (Roughly 100x the surface of statically linked binaries that don't link with libc -- which is evident by the insanely bloated OCI images that result from packaging what you need to run)
Modern compiled languages that have sorted toolchains makes it pretty easy to produce "production grade" os/arch specific binaries that can survive almost everywhere. You compile build a statically linked binary for each architecture to overcome the challenges of varied Linux runtime environments (see Linus T's frustrations with Linux and software distribution - it's not like it is easy to begin with). Go and Rust do this well.
So you end up having to containerize everything in ephemeral containers to lock down the execution environment while retaining some speed. But of course it isn't that simple, because if you depend on access to weird hardware and/or you run on custom built machines you have to detect this and ensure the application inside the container gets access to the things it needs from the container. So you have to fix that.
In a way that is almost completely invisible to the developer.
All of this has to be understandable and _reduce_ complexity for developers and operators so at the very least you don't follow the Python philosophy of "just throw another layer of complexity on it and make the instructions another page longer".
40-50kLOC later (in Go and Python, I have lost count) of code to try to make the problems go away, and I have something that is on the verge of actually being usable in a production environment for taming wayward Python code.
The easiest fix? If people could stop using Python because they don't want to learn a language that can produce something that is easier to distribute to users.
Believe me, I have spent months now trying to make Python work properly in a challenging environment. The only way this "worked" before was by just lowering standards to where the definition of "works" is flexible enough to count daily dumpster fires as "nominal". And of course people don't care. Python fosters a "it works for me" mentality where people don't know and don't care what it is like to be on the receiving end.
90% of problems I have because of Python would just disappear if people used languages that can produce robust binaries with limited exposure to system peculiarities. But that kind of requires people to understand why it is a problem in the first place. And people generally don't bother to know.
Comment by srean 1 day ago
Comment by thr1owaway9621 1 day ago
Cython is not in any real sense a replacement for a modern data/ml stack.
Comment by srean 1 day ago
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.
Comment by pdpi 1 day ago
Comment by srean 1 day ago
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.
Comment by mountainriver 1 day ago
And that even shrinks by the day
Comment by sideway 1 day ago
Comment by hocuspocus 1 day ago
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.
Comment by mountainriver 1 day ago
ML still has a depth of libraries that can't be replicated easily but ML work is decreasing by the day with LLMs.
Comment by nijave 1 day ago
Comment by hocuspocus 1 day ago
Comment by wolvesechoes 22 hours ago
Because the feedback loop of writing few lines of Python inside Jupyter cell is much shorter than with your currently favorite AI tool. It costs less too.
Comment by mountainriver 10 hours ago
Comment by locknitpicker 1 day ago
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.
Comment by MeetingsBrowser 1 day ago
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.
Comment by throwaway2037 1 day ago
Comment by fastasucan 13 hours ago
Its like saying "if you want a car that is a bit sporty, wouldn't it be best to buy a Lamborghini??
Comment by dsign 12 hours ago
Comment by vadansky 1 day ago
Comment by marcosdumay 1 day ago
You don't use a static language because you want the exceptions, but the type checking can still statically validate most of your code.
Comment by ethagnawl 1 day ago
Comment by falcojr 1 day ago
Comment by chuckadams 1 day ago
Comment by ocamoss 1 day ago
Machine-checked documentation is always valuable, IMO
Comment by UltraSane 1 day ago
Comment by lijok 1 day ago
Data tooling
Talent pool
Libraries for customers
Brownfield codebases
Academics
I can keep going…
Comment by bborud 1 day ago
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.
Comment by chuckadams 1 day ago
Comment by necovek 1 day ago
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.
Comment by chuckadams 15 hours ago
As for the screaming, I'm talking about pip, which has the unique property of just refusing to work by default unless you use --user (what most other package managers now call "global") or are in one of these pretend-global environments. Ultimately it's just a paper cut, not a show-stopper, but it points to not just a failure to address the pain points of the package ecosystem, but a seeming refusal to do so.
Comment by necovek 8 hours ago
You are right that there are many problems with packaging in Python, and yet I feel like virtualenvs are the smallest of those.
I believe we also need to compare tech this old to tech from the same era: obviously newer ecosystems had the benefit of hindsight, but how does managing dependencies compare between Perl and Python, for instance?
The biggest problem with Python packaging is — IMO at least — that it is actually attracting so many evolutions and proposals that ot is hard to stay on top of if you do not make Python packaging your core interest.
Comment by chuckadams 4 hours ago
Comment by nijave 1 day ago
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"
Comment by bborud 18 hours ago
And yeah, I have written a lot of code to insulate Python in containers while allowing meaningful access to hardware and services. While at the same time not heaping more complexity and cognitive surface at the developer. Including writing my own container software to actually understand what's involved at a more detailed level so I know what I'm doing when trying to make this work with existing container software. (No, I don't run any of my container managers in production since I don't want to maintain it -- but this also means a bit more complexity in using existing ones)
It may be "easy" in trivial cases, but it is very far from easy if you want to make something that can cater to a wide range of scenarios.
Comment by nijave 3 hours ago
Why do you need to write code to insulate Python in containers?
At the simplest level, you can add the flags to the container runtime (network host, host ipc, host process namespace) to turn off all the namespacing besides filesystem and the Python container runs just like a non containerized process.
An extreme example
https://github.com/home-assistant/docker-base (Debian, Ubuntu, Alpine base images with Python for arm64 and amd64)
& https://github.com/home-assistant/core/blob/dev/Dockerfile (Python app built on those with >1000 deps)
And even there most of the custom code is just running a ton of combinations of inputs against docker build. The OCI container gets rid of "wide range of scenarios" for you standardizing the runtime environment
Comment by Hizonner 1 day ago
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.
Comment by IshKebab 1 day ago
For people who don't have a choice, type checked Python is better than nothing.
Comment by locknitpicker 1 day ago
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?
Comment by necovek 1 day ago
Comment by globular-toast 1 day ago
Comment by thrance 1 day ago
Comment by necovek 1 day ago
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.
Comment by scuff3d 1 day ago
Comment by hedora 1 day ago
Comment by kingstnap 1 day ago
Comment by vitorsr 1 day ago
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).
Comment by ActorNightly 1 day ago
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.
Comment by TremendousJudge 1 day ago
Comment by mort96 20 hours ago
The interpreter doesn't know about static types.
I agree that they should've made typing more a proper part of the language and not left it in this weird half-defined state of "standard syntax and some standard typing imports but undefined semantics". But it's not just a matter of enforcing existing types.
Comment by voidUpdate 1 day ago
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
Comment by vitamark 1 day ago
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
Comment by brandnewideas 1 day ago
Comment by samsartor 1 day ago
Comment by stephbook 1 day ago
Does a == b true, if all elements are the same? Does it return an array of booleans? It's anyone's guess!
Comment by NewJazz 1 day ago
Comment by tzs 1 day ago
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 $DiHere'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.Comment by datsci_est_2015 1 day ago
Another example might be if you have a domain specific representation of equality (e.g. class Equality)
Comment by voidUpdate 1 day ago
Comment by agons 1 day ago
```python df.filter( pl.col("foo") == pl.col("bar"), ) ```
Sqlalchemy does something equivalent too, and I'm sure there are many others.
Comment by datsci_est_2015 1 day ago
Could enable a different interface into approximate equality for floating point numbers: Equality.approximate(iota: float) -> bool
Comment by olooney 1 day ago
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.Comment by kuschku 1 day ago
Comment by xemdetia 1 day ago
Comment by voidUpdate 1 day ago
Comment by hmry 1 day ago
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)
Comment by throwaway894345 1 day ago
Comment by data-ottawa 1 day ago
I understand why those exist, but they’re pure evil.
Comment by throwaway894345 1 day ago
Comment by VoidWarranty 1 day ago
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'.
Comment by necovek 1 day ago
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.
Comment by fasterik 1 day ago
Comment by necovek 1 day ago
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.
Comment by fasterik 1 day ago
Comment by necovek 1 day ago
Comment by Smaug123 1 day ago
Comment by necovek 1 day ago
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... :)Comment by Stevvo 23 hours ago
Comment by big-chungus4 22 hours ago
Comment by AsthmaBoy 1 day ago
Perhaps you meant statically typed?
Comment by VoidWarranty 1 day ago
Comment by ActorNightly 1 day ago
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.
Comment by VoidWarranty 1 day ago
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.
Comment by ActorNightly 10 hours ago
There is no way that Rust will be faster than simply specifying tests for an agent to run after it has generated code.
Comment by VoidWarranty 5 hours ago
Comment by blahgeek 1 day ago
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.
Comment by KolmogorovComp 1 day ago
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.
Comment by thr1owaway9621 1 day ago
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?
Comment by cptmurphy 21 hours ago
Comment by Syntaf 20 hours ago
Went from type checking taking ~10 minutes in CI to now taking ~15 seconds and runs on pre-commit.
Absolute game changer, I think we spent $10k in claude credits and did the entire mypy -> ty refactor in about 3 weeks.
Comment by emeryberger 13 hours ago
RightTyper is a Python tool that automatically generates type annotations for your code. It monitors your program as it runs and records the types of function arguments, return values, local variables, and class fields — with only about 25% runtime overhead. This makes it easy to integrate into your existing tests and development workflow, and lets a type checker like mypy catch type mismatches in your code.
Comment by stephbook 1 day ago
Python's type checking ecosystem truly is a mess.
Comment by semiinfinitely 1 day ago
Comment by pvdebbe 1 day ago
Comment by throawayonthe 22 hours ago
Comment by throwawayffffas 1 day ago
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.Comment by HelloNurse 21 hours ago
Moreover, the extremely dynamic execution and lack of compilation in Python means that determining in a lint step whether "there is no + operator for int and dictionary" is essentially impossible: if you find some __add__ or __radd__, you need to trust its declaration that it accepts certain types or not and hope it's still there at runtime; if you don't find it, it might exist at runtime when operator + is actually used.
A compiled and sufficiently static language, on the other hand, can inspect source code and libraries and reason about potentially infinite sets of definitions and prove that functions exist or don't exist.
Comment by throwawayffffas 6 hours ago
I don't think the operator overloading is such a big issue since you can do the same kind of tracing for the overloaded operator.
The main issues would be metaclasses and all sort of monkey patching etc. i.e. anything that could mutate a type at runtime because the linter can't know if the mutation has happened.
Unfortunately I do not have the time nor patience to introduce the sixth type checker.
Comment by rzmmm 16 hours ago
Comment by leni536 1 day ago
"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...
Comment by zerof1l 1 day ago
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.
Comment by __mharrison__ 1 day ago
Comment by braiamp 1 day ago
Comment by ocamoss 1 day ago
Comment by ryanshrott 1 day ago
Comment by ghostly_s 1 day ago
Comment by Someone 1 day ago
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.
Comment by ForHackernews 1 day ago
Comment by prodigycorp 1 day ago
Comment by shermantanktop 1 day ago
> 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.
Comment by insane_dreamer 1 day ago
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.
Comment by pantafive 1 day ago
Comment by faangguyindia 1 day ago
Comment by shevy-java 1 day ago
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.
Comment by woeirua 1 day ago
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.
Comment by voidUpdate 1 day ago
Comment by cryptonym 1 day ago
When something is easier/requires less context, it tends to work well for both human and LLM.
Comment by vips7L 1 day ago
Comment by hedora 1 day ago
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).
Comment by vips7L 1 day ago
Comment by datsci_est_2015 1 day ago
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.
Comment by hedora 1 day ago
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)
Comment by datsci_est_2015 1 day ago