Python's pre-declared constants are kinda weird
sebsite.pw
sebsite.pw
Every single new, large feature in JS has been full of quirks. The same cannot be said for Python; the exceptions are few and far between and more often than not they're not actually exceptions but rather something bound to core language fundamentals which once understood don't present a challenge because there is an actual underlying consistency.
In about the same timeline, Py went from threading to async-await, which created classic blocking vs nonblocking mismatches. The whole 2 to 3 breaking migration was also still a big deal in 2015.
(Note some of these are outdated, but some of those only mention that towards the end of the section rather than the beginning)
https://www.oreilly.com/library/view/javascript-the-good/978...
Helped me get a more pragmatic approach to use that chaotic mess of a language and environment. Just use what works, ignore the rest (but I also ignored some specific advice from the book and used what worked for me).
I challenge you to find Python behaviors as weird and off-putting as anything here: https://wtfjs.com/
Python has weird file imports (no relative ones either), broken package management, historical differences between asyncio and blocking that still cause issues, threading/GIL caveats that trip up even experienced users, indentation for scope (esp weird given it was designed for REPL), weirdly no anonymous functions, 2 vs 3 (mostly gone by now), the __init__ and __init__.py stuff, namedtuple vs dict vs object, `global`, and a whole mess with type-linting if you're going there. You have to deal with all those things every time.
Here's one little Python footgun that everyone hits and is also annoying after:
def func(array=[]): # default value is empty array, right?
array.append("asdf")
print(array)
>>> func()
['asdf']
>>> func()
['asdf', 'asdf']This is so much so that it is described in the Zen of Python: There's preferably One Way to do things, but that one way may not be obvious.
A gun becomes a footgun when you put it in the hands of someone who doesn’t understand how it works and they promptly blow off their foot.
I don't know why you think it should be so common, because mutating a parameter with a default value doesn't make a lot of sense in the first place. But also it's one of the single most well-documented things about the language, and it's also historically been found useful when invoked intentionally (https://stackoverflow.com/questions/9158294).
Also, we call that a list, not an array.
> no relative ones either
It certainly does have relative imports, and I have no idea what you think is "weird" about the import system.
If you're talking about dynamically importing from a string path, that, too, works just fine with a relative path. It's just that relative paths are relative to CWD rather than the source file, just like any other time you open a file.
If you're annoyed that you don't just specify a file path, keep in mind that a Python module doesn't have to correspond to a file at all. What you ask for in this case is impossible and nonsensical.
> weirdly no anonymous functions
Incorrect. `lambda`.
> differences between asyncio and blocking
This is like saying "differences between cars and traffic jams". It makes no sense whatsoever.
I could go on, but the short version is that you do not know what you are talking about.
Python has "relative imports" in that you can import relative packages, not files. So the weirdness is, even within my little app, I have to define packages for everything rather than just doing like require("./common.js") like you'd expect in a scripting language. https://stackoverflow.com/questions/714063/importing-modules... . The __init__.py thing is weird in of itself because different Py versions have different rules, and without __init__.py you accidentally make it a namespace package: https://stackoverflow.com/questions/448271/what-is-init-py-f...
> Incorrect. `lambda`.
I meant for general functions, not single-line only. Python code tends to have lots of one-off defs. It's common in other langs to pass around anon functions like outer((var){ ... }). Instead Python covered a subset of those use cases with a whole separate context manager feature (`with`). Why are lambdas single-line only, probably because indents-as-scope would make multiline too weird.
> This is like saying "differences between cars and traffic jams". It makes no sense whatsoever.
I'm sure you've done async coding and run into the classic issue of accidentally doing blocking I/O in code that shouldn't be waiting. For a long time, Python had no such thing, so something like a web backend would use a thread pool which adds a lot of overhead the more IO-bound your handlers are. Then they added asyncio to Python. So now the libraries are mixed on whether or not they do things async. Even psycopg2 needed a whole rework to psycopg3 for this, and it still has caveats https://www.psycopg.org/psycopg3/docs/advanced/async.html#as... . Django has some complexity around async vs non-async too. I don't fault Python for not thinking of this back then, but the end result is confusing.
JS had an event loop from day 1, so it's much safer to assume there that IO is non-blocking. This was part of the motivation for Node.js. There's still code predating async-await, but it's easy to wrap that.
That default parameter is a mutable global variable and languages like Rust have effectively banned mutable statics for good reasons that I personally am discovering myself by messing around with UnsafeCell inside statics and even I personally am starting to think that mutable statics are at best a necessary evil, but not one you want to rely on as your go to solution.
For example, why would you expect `Boolean("false")` to equal `false`? It's a string, and bears no relation to the Boolean type. [0]
[1, 2, 3] + [4, 5, 6] == "1, 2, 34, 5, 6"
or parseInt(0.000001) == 0
parseInt(0.0000001) == 1
or "" + 5 == "5"
"" - 5 = -5Well, `+` is a string operator. There's no infix array concatenation operator, so makes sense.
> parseInt(0.000001) == 0 > parseInt(0.0000001) == 1
Never knew about this one! Quite funny actually and I'm curious why that happens. I suppose because `0.0000001` is represented as an exponent rather than a decimal? Although I haven't seen `parseInt` used since 2015, you should use `Number`.
> "" + 5 == "5" > "" - 5 = -5
iirc
`+` operator: - If LHS is a string, concatenate - Otherwise, cast to Number and perform arithmetic.
`-` operator: - Perform arithmetic.
All of these are explainable, and never catch anybody competent out in practice. And, since TypeScript is the norm in a lot of places now, it's never an issue.
The name parseInt suggests it takes a string. Especially "" - 5, why?
They each have different quirks (some will parse 'a123' as 123, others will handle scientific notation etc). The only reliable way of doing this is doing a regex followed by parseInt... which is definitely a footgun IMO.
What makes this weirder than other languages detecting if they’re an import or an invoked file?
__this_file__ == “__launch_file__”
or similar. I understand python values “only one way” of doing things, but it would be helpful for readability.
if __main__:
There are no magic strings to remember and the IDE can help you look up variables.I honestly think one of the main reasons why python is so successful is as simple as:
print "Hello world!"
...and later: print("Hello world!")
It might not seem like much, but when you're asking someone to pick up a language outside of a classroom setting, it's just much more intuitive than: echo "Hello world!";
or: console.log("Hello world!");
or god forbid: public class GFG{
public static void main(String[] args) {
System.out.println("Hello world!");
}
}
These little things matter in the long run, even if they don't seem like they matter when you're already fluent.But Python breaking hello world in version 3 was crazy.
It makes perfect sense, but also is not an example of what TFA is about. In fact the entire point of having that `if` check is that `__name__` is not a constant.
https://nodejs.org/api/esm.html#importmetamain
node also has __dirname and __filename from the good old days.
Nice to see it get attention this time.
It would be nice if, just once, we could have a thread about interesting esoteric Python behaviours without people taking it as an invitation to dump their laundry list of things they personally don't (or do, for that matter) like about Python, or to make inane comparisons to other languages (especially JavaScript, for very unclear reasons) while being simply uninformed.
I’ve of course certainly heard of, seen, and used `assert`, but more often than not, outside of pytest, I see its use way more in potential footgun scenarios—I doubt that many people know that assertions can be silenced, and that they’d probably be better off raising exceptions in many cases where they’re using `assert`.
I would not be surprised in the least if that pattern existed in the wild. In fact, it's quite common to see this in C/C++ codebases too: people will use assert() to check a security-relevant property, and then disable those checks in their release builds "because it can't happen".
The most direct way to find out about `__debug__` is to read `python -h` (or the usage message, which is not all that easy to trigger) in full, and then head over to the documentation.
> Does it actually get used that often in real world code?
https://github.com/search?q=language%3APython+%2F%28%3F-i%29...
Assigning to __debug__ wouldn't do anything to the compiler as it never actually reads the variable, so assignment would just cause weirdness from other use
True = 1
False = 0
then later these got added to the language. In Python 2 you could still reassign and swap them so that 'if False' was actually true!
True, False = False, True
Python 3 you could no longer reassign them.
Common language design boners:
- Not building in strings. That's now in the past. Everybody has strings. (Well, C...)
- Not building in multidimensional arrays of the numeric types. Everything that number-crunches needs them, and having multiple definitions is Not Fun and may lead to expensive re-copying between different libraries. This is an enormous blind spot in language design. It's one of the reasons FORTRAN, which has good multidimensional numeric arrays, is still often used for number-crunching.
- Not standardizing the small vectors (vec2, vec3, vec4) and their matrix friends. Graphics code depends on these, and it's really annoying if there are multiple slightly incompatible implementations. Especially since GPUs have hardware for those types, and you want CPU and GPU to use the same representations.
- Not having arrays of bits. Pascal had PACKED ARRAY[0..N] of BOOLEAN but that was lost in later languages. It's useful to have that as a language construct, because most modern CPUs have good hardware for dealing with bit strings, and you'd like the compiler to use it.
Most useful languages acquire these features, but, when they come in late, there are multiple similar implementations, and libraries made incompatible by depending on different implementations.
(Amusingly, when Second Life switched from Linden Scripting Language to Luau, they initially had True, TRUE, and true all in use, as different types with different semantics. I was able to persuade the devs to unify the boolean types.)
Several times now†, people make a language where the way a for-each loop (for each Goose in Geese ...) works is that there's a single variable Goose and each time around the loop we change which value is referred to by the Goose variable. This seems intuitively like a reasonable way to do this. But it's wrong and eventually your programmers will get nasty surprises. What you actually should deliver is an implementation where each time around the loop there's a new variable named Goose, that variable goes away at the end of that iteration and will be replaced by the next one, with the same exact name.
† At least Go and C#, I think there are others
Is this because a closure inside a loop will capture a reference to `Goose`?
I think that this is a capture problem not a variable problem. The closure should always do the right thing and capture the value of all variables (not just ones inside the loop), instead of capturing the reference to the variables.
Then the general problem is fixed to match what developers expect, instead of a specific instance of that class of problems being fixed and working differently to how other captured variables work.
Most trouble in this area came from the iteration variable outliving the loop. That's not good when the iteration variable is a pointer. In C, it often is, and at the end of the loop, it points to an invalid address. It was a change to C (when?) to make the iteration variable go out of scope before code after the loop could get at it.
Now your "lalanthran closures" can't mutate the world because they work exclusively with copies not references, if they try to mutate something then whatever they're touching was just a copy not the real thing.
That is true. I still don't like the idea of "Here is a general rule. It applies everywhere but $HERE." Whether that general rule is "All captures are by value" or "All captures are by reference", the rule should not have exceptions based on context in the code.
A better tradeoff would be to have the general rule (whatever it is) apply everywhere, along with syntax for capturing (or not, depending what the default is). I'd rather have it grab everything by value, and for those things that are susceptible to race conditions (because more than one closure is modifying it), explicitly annotate it with a sigil (`&`, or a keyword, or similar).
I mean, in pseudocode, when I see:
... variables x, y and z are declared and used in this scope ...
return (x, y, x) => { ... }
I don't want to have to examine the surrounding scope to know whether or not `y` is susceptible to a race. I'd rather just see: ... variables x, y and z are declared and used in this scope ...
return (x, &y, x) => { ... }
An alternative viewpoint is that many languages have immutable variables and they seem to be getting along just fine without needing mutation on variables, shared or otherwise.But the rules didn't and still don't have any such exceptions.
> A better tradeoff would be to have the general rule (whatever it is) apply everywhere, along with syntax for capturing (or not, depending what the default is)
This "solution" is how it works in C++. We can thus castigate the programmer for writing the wrong runes in their captures list and never for a moment doubt that we got it right when we introduced so very many footguns...
Tony Hoare's observation applies "One way is to make the program so simple, there are obviously no errors. The other is to make it so complicated, there are no obvious errors."
> An alternative viewpoint is that many languages have immutable variables and they seem to be getting along just fine without needing mutation on variables, shared or otherwise.
Sure, and one of the astonishing things in C# or Go before they fixed this is that you can indeed have immutable variables which change, even though that's silly - the language can decide that you mustn't change Goose, but it doesn't need to obey its own rules because it will change it for each loop iteration.
I'm not sure exactly which features are responsible (I'm inclined to blame templates), but C++'s std::vector<bool> is a rough edge. For those unfamiliar, the standard specifies this vector template in a way that's not compatible with other vectors.
It kind of started with vectors as the very first feature (I was sick & tired of libraries reinventing their own `Point`/`VectorN` in incompatible ways).
I'd consider LSL to have been foundational in my ultimate interest/career in software engineering. The strict typing, very usable compile/runtime errors, and good documentation/examples made it so easy to pick up as a teen. Not to mention as long as you didn't edit/save a script again it would always run the same regardless of updates.
Definitely agree on multidimensional arrays. I feel like efficient arrays in general are underrated in high-level language design.
The thing you want is what Rust delivers in the box, &str a string slice reference type, in Rust's case the "string" is UTF-8 encoded text. On the bare metal the way to represent this type is as a "fat pointer" typically a pair of registers, one with the address of the first byte of the string and the other with a length.
C should have fat pointers, they were proposed, for IIRC C89 but the proposal was rejected. That's pretty sad, the fat pointer is expensive to the point of maybe feeling extravagant on a PDP-11, but by 1989 that's long gone.
More ridiculously C++ didn't get this type (which it eventually called std::string_view and provides in its standard library not as a built-in) until 2017, years after Rust 1.0 shipped. In the meanwhile C++ just did not have a sensible way to do this, strings are hard apparently.
The string buffer feature, allowing you to actually make strings is less important, as you say it will need an allocator and so on very bare metal you might not have this - but the string slice reference doesn't need an allocator.
I think it's worth delivering the basic "it's a growable array type, duh" implemenation of the string buffer type, which is what Rust's String type is, but C++ chooses to ship an oddly specific small-string optimized version as std::string right from the offset.
set(foo, false)
removes foo entirely. What if you want foo to have the value False? Even besides the unintended behaviour where 0 is coerced to a boolean value, this function seems poorly designed.Sure, it's not a language to write a web browser or game engine in. And it is slow. But it has some very strong niches outside of ML/Data science. Personally, I love it. To each their own.
uv + PEP723 make this even better.
[0]: https://peps.python.org/pep-0790/#schedule
[1]: https://peps.python.org/pep-0781/#backwards-compatibility
https://github.com/nucypher/constantSorrow/blob/master/tests...
Ruby has such a nice holistic consistency to it. With a few exceptions, it feels like it was conceived of by one person with a core idea in mind. Python feels like a mess.
Likewise, it made classes incredibly easy to extend, which led to a monkey patching bonanza and far too much magic everywhere (Rails being by far the worst offender but not the only one). It meant having to keep too much stuff in your head and needing deep framework/library familiarity just to be able to understand basic code.
In the end, I feel like the incredible flexibility was Ruby's undoing, not just lack of library availability for a specific popular application. I was a Ruby zealot at one point but it began to lose its lustre not because it wasn't beautiful in theory, but because it was inconvenient in practice. In some ways, Python's restrictiveness became its greatest attribute. Then Python 3 helped to fix a lot of the inconsistency.
Both Python and Ruby have a consistent logic to them, just not consistent with each other. Just as all attributes are methods in Ruby, so all methods area attributes in Python, etc. Things get much easier in either language if you stop fighting their internal logic.
This is just my personal opinion with no data to back it up, but I suspect that Python "won" because it has excellent Windows support, while Ruby doesn't. Even a decade ago, Python's website offered an official native Windows installer [0], while Ruby's website [1] still points you to a third-party installer, which doesn't even have native support since it uses MSYS2 [2].
Most non-developers use Windows, so if you're choosing the first language to teach a large group of people, good Windows support is fairly important. Python being the "default" introductory language gave it a huge number of users, then I suspect that everything flowed down from there.
[0]: https://web.archive.org/web/20160824235759/https://www.pytho...
I also wonder how many people gave up on Python just because the installer doesn't put it in your PATH. You'd install Python then no python, wtf. Ok so https://discuss.python.org/t/python-command-not-found/22255 ... Then you fix it and it runs the wrong version of Python.
I think you might be able to go an additional decade backwards. Back in college most of my friends were on windows and one of them was using python for class projects.
Yeah, Python has had good Windows support at least 15 [0] or 25 years [1], depending on how you count it.
> Back in college most of my friends were on windows and one of them was using python for class projects.
Well it's always been possible to install Ruby on Windows too, it's just that Python supports it so much better.
Ruby's most important error is that it does not support namespaces. This by itself makes it a far less scalable language than Python.
SWIG[0] makes working with C libraries trivial for over a dozen programming languages; Perl, Python, and Ruby included.
And those reasons are?
IMHO, both approaches have merit depending on the situation. I lean towards using SWIG until and unless the situation warrants a hand-written solution due to the boilerplate nature of these types of libraries.
In an age where people are still standing by C over memory-safe systems programming languages, I feel quite comfortable depending Python for the great many things that Python is good at.
If you've ever read through FORTRAN code from a mathematics department or MATLAB/C/C++ from (non-software) engineering disciplines, then you probably understand why productionizing a jupyter notebook is definitely not the worst of all possible worlds.
That said, I find it the nicest, cleanest option of the three. I still wouldn't use it for large and complex projects. I really like it for stuff where one might otherwise use shellscript. It's way way better than shellscript... except if it's all about files and running external commands.
This is exactly what I remember being said about C (which I agree with) and often given as a reason why higher level languages like Python or Java have so many protections against things C/C++ allowed (memory management being the biggest one of course). Very funny, and I assume not coincidental, to read this about Python in the modern programming landscape.
For little utilities, it’s faster than a lot of alternatives - just start the interpreter, no compilation needed.
It’s all relative, but if you view it as replacing bash scripts for renaming files or running other tools, it’s 100x better.
Have you tried using `uv`'s newer tools? They help a lot e.g. with linting speed, lock management, package dependency separation, correct python version mgmt and no need to fudge with venv.
The fact that Python has become the language of choice for machine learning and data science is not a language issue.
We open sourced the implementation https://github.com/janushendersonassetallocation/loman
import numpy==1.5.4
and the code gets exactly the version it wants.https://packaging.python.org/en/latest/specifications/inline...
And it's weird how you import files. They're dot-separated packages that resemble file structure but not exactly. NodeJS has a self-explanatory require("./foo.js") or "../foo.js". The newer JS `import` syntax is annoyingly different from `require` but not terrible.
My CLI tools publish from Github to PyPI so that I can run tools with just `uvx sql-agent-cli` or `uvx dlna-here. Nothing for me to handle downloading (directly myself), no environment to manually setup, portable (Linux, Windows, Mac, ARM, x86). Easy for agents to run from a skill.md file without any other prereq than uv.
Really useful library ecosystem to leverage. No more shell scripts, or TS/JS/PHP backend services. I've even used Python on devices I've built around Raspberry Pi Zero 2 boards.
You didn't use Perl before? https://xkcd.com/353/
At least it’s Python/Jupyter and not R, SAS, or MATLAB.
> I often work with data scientists and have to productionize their jupyter notebooks
I'm not a huge Python fan, despite working with it fulltime, but this feels like mixing correlation and causation. Data scientists would not be writing good, optimized code in any language.
> and it’s way too easy to do the wrong thing
is there another programming language where you believe a data scientist is going to have an easier time writing correct code than Python? Do you think C or Rust or JavaScript or C# make it harder to do the wrong thing?
I'm bringing Y2K back next!
Took me down some rabbit holes, but interesting to see the chatter about the fix here:
https://github.com/python/cpython/issues/80233
Initially you could reassign True,False but that was verboten with the switch to python 3! The walrus operator was the one simply an oversight.
https://python-history.blogspot.com/2013/11/story-of-none-tr...
Explanation from Guido himself
The only thing good I can say about Python nowadays is that it's easy to get started for the first five minutes, and then you'll have to deal with all of its weirdness: significant whitespace, truthiness, duck typing, GIL, distribution/packaging, etc, etc.
I was a big fan of Julia as the potential replacement for Python for science for such a long time and I had evangelized it a lot previously, but recently I've been more and more convinced that JIT/multiple dispatch was only good if you already know how to program well to begin with, which for a lot of academics who are not working in computer science, they write quite horrific code. I think it may be better off to skip Python altogether and write your code in a statically typed language to begin with.
In my experience the difficulty is understanding what exactly is important when teaching someone something new. And it largely depends on the goals. What you’d teach some biology undergrad is going to be very different from what you teach a bunch of robotics team high schoolers (and no you’re not teaching them the best language for controls and embedded).
Embedded firmware, probably C/C++/Rust. Not the answer you want to hear, but these are the languages for bare metal applications. Of course, if you are just using an Arduino/Pi, just use their SDK for their hardware on whichever language they support.
Those two fields are just not very friendly towards beginners in general.
It was designed as a teaching language for academics to stop writing bad code, but now I just get LLMs to do research/science for me with it for fun.
It depends. If you're a teen I'm mentoring, you're probably starting with some Scratch to drive Lego robots around. Then on to Python to drive the same robots around but now with more fun!. Probably because you absolutely couldn't stand the standard line follower solution of jittering back and forth and you sniffed out the existence of better control loops you cannot realize in Scratch. If you're on the FIRST Robotics team you're probably doing Java or Python, mainly because that's just what we've geared up for (and this is really the core theme for me at least: at introductory levels, what really matters most is whatever is most readily accessible to you and whatever kit you already have).
If you're doing your own stuff and you're a newbie with absolutely no opinions on where you started or what you were trying to do, you'd probably start poking around with an Arduino or similar, so you could write MicroPython (it's Python but you squint your eyes a bit!) or C. There's so many great kits for beginners.
In the context of my comment: what I meant is that you wouldn't decide, "the professionals do it in C++, C, or Rust, so we'll start with one of those." I'm going to give you a recorder (Python) before I hand you bagpipes (C++).
Once you learn that Brian Harvey, one of the two main designers of Snap!, was one of the principle people behind Berkeley Logo (which itself was a variant of Lisp, though that wasn't clear to me when I was learning Logo at age eight), it all starts to become clear. Snap! is itself nearly a Lisp, just lacking macros (and Brian Harvey is trying to figure out how to add a macro system to Snap!, with the primary challenge being making it comprehensible in graphical-blocks form).
In the past, the usual answer to people who need a programming language but did not want to learn programming was to give them a domain specific language that focused on solving the specific problem they wanted to solve.
> I think it may be better off to skip Python altogether and write your code in a statically typed language to begin with
Having a good REPL is a huge advantage for beginners (and expert users too), but I'm not aware of any (popular) statically-typed languages with a good REPL.
Despite Python's many faults, it's easy to install (especially on Windows), it has a large standard library, there are third-party packages available for essentially everything, it comes with a user-friendly REPL out-of-the-box, and it gives comprehensible error messages. I'm not really aware of any other (popular) languages with all these attributes.
scala's repl is decent. It has its annoyances, but so does python's (white space sensitivity + repl + terminal emulators stuck in the late mid century don't mix).
The idea is fine, I guess, although I certainly don't care for it. Where it gets most nasty is that whitespace that looks the same (in your editor) might not be equal and will cause you pain.
First off, what editor could I end up using in 2026 that makes this a realistic problem? Even the most basic editors I know of have options to convert tabs to spaces automatically (and any responsible teacher will tell the student to indent with spaces), and to continue the previous line's indentation automatically.
Second, modern Python is stricter about this, and also gives clear error messages.
This is why idiomatic Python only uses spaces for indentation.
By "weirdness" here you apparently mean not having to worry about matching up curly braces, and getting what you want automatically just for indenting your code the way you're supposed to indent it anyway... ?
> truthiness
Which is different from how it works in other similar languages, how exactly?
> duck typing
Which is weird, how exactly?
> GIL
You can go a lot further than five minutes in Python without having to worry about threads at all, and if you do attempt threading, unless you're writing C extensions, the worst thing the GIL does is deny you the multi-core processing you thought you were going to get.
> distribution/packaging
Tons better now, but it was honestly never difficult, people just didn't care.
They are contrasting with languages where true is true and 1 is 1; but true is not 1, and 1 is not true.
> Which is weird, how exactly?
Sometimes if it looks like a duck and quacks like a duck it can still not be a duck.
> You can go a lot further than five minutes in Python without having to worry about threads at all
Unless you specifically want to do multithreading.
> the worst thing the GIL does is deny you the multi-core processing you thought you were going to get.
That is the worst thing because I wanted that multi-core processing, that's the whole reason I wanted to do multithreading.
Now it feels like a weird PHP itself that is slow, brittle, and dangerous to write code at scale in.
The loose typing, potluck standard library, and horrible package manager (insofar as the community does not know how to package code) all feel so dated.
It’s really worth a second look.
GP: python has baggage because it is old
P: that can be changed. Look at PHP
Me: PHP still has baggage, despite its evolution. You can't really get rid of it without making major breaking changes.
I still really enjoy using python though. It's not really a fair comparison because I hadn't used PHP and Perl for as long but I just don't hit some mystifying issue every single session like I did with those languages when I'm using python. I honestly have never even read about that __debug__ constant. It's fun to hear about it but it's just not something that's comes up much.
The Python community has spent the last 15 years refusing to improve in any meaningful way, or to learn anything from their peers. As someone who used to choose only jobs that would let me work with Python, I’ve gone through every phase of grief, and now just try to forget that it exists.
But to describe the Python community as "spen[ding] the last 15 years refusing to improve in any meaningful way" is just laughably wrong. I can't give details as I haven't been doing much Python work, but even so I know of multiple changes, such as the typing system, or packaging improvements, which have significantly improved the language AFAICT. If there's a reason why you would not consider those to be "improv[ing] in any meaningful way", please enlighten me.
But reading through a Python script that I had Claude Code write for me taught me another one: apparently there's now a / operator on strings, because Claude wrote `path = "some" / "dir" / "filename.txt"` without importing anything outside of the stdlib. I presume it is shorthand for calling os.path.join and will therefore apply the correct path separator on Linux vs Windows.
Which I will say is overstated but I can see where they are coming from even when you bring into scope things like types, async, etc.
First types. This is what the type hints in Python allow you to do:
def foo(x: int) -> int:
return x
foo("hello")
We can say that this is great Python has type hints, but at the same time the lesson they learned is wrong because the feature to have isn't type hints but actually enforcing them so the above code cannot be written. In my eye, this is an anti-feature.Second async, this is also the wrong lesson to have learned from other languages. Adding async/await is a bandaid over the problem that the synchronous imperative model clashes with asynchronous distributed semantics. The async/await keyword are a way to try to bridge between the two, but it creates a "function coloring" problem that all these languages which added async/await have.
The lesson Python should have learned is to not add these keywords and go back to its glue language root, allowing actually natively asynchronous languages to coordinate asynchronous processes, while Python code handles the synchronous core. Python doesn't have to be everything, the wrong lesson was to try to be the one language to rule them all.
You also brought up the packaging improvements, which I feel were the wrong lesson learned. The problem with the Python packaging ecosystem is well known since it's been expressed in the XKCD comic. The lesson from other languages is: one blessed compiler toolchain integrated into the packaging story makes for a better user experience. This is the npm, cargo lesson. For Python to really learn it, uv or equivalent would be the blessed way of managing Python projects. Instead it's still a very fragmented landscape with many competing solutions, which goes against Python's own zen.
Moving on to pattern matching, again I feel the wrong lesson was learned. Pattern matching is a feature from the functional paradigm that in my opinion became more popular with developers when they became exposed to it in Rust, and so Python joined in and added the feature as well. But the reason it's such a nice feature in Rust is because it will refuse to compile any code that does not do exhaustive matching on all variants. This is great because it catches problems early and forces you to consider the non happy path where things can error. So pattern matching alone isn't the feature it's pattern matching PLUS structured Enums and exhaustive patterns.
So in Python you can do this:
x = 3
match x:
case 1:
print("one")
case 2:
print("two")
print("done")
Output will be "done" rather than an error on the match, which is what should happen if they had learned the right lesson, because without exhaustive matching this is no better than an if or switch. Again I consider this an anti-feature -- better to not have at all if it doesn't work as expected.Anyway, I'm not trying to say Python is bad, I'm just saying I get where the other poster is coming from when they say Python has refused to learn the right lessons. Although I would not agree they haven't improved a lot over 15 years.
Many people like them however. And you’re being way too charitable to that dumbass comment.
It sounds like scoffing at a Silver medalist to me.
In fact, Python is better at what it does than almost anything from the era. That’s why we use it. Nothing is ever going to be perfect because better solutions are emergent, and reverse time travel does not exist.
Okay that's where you lost me. You can talk about the language from your perspective, and like I said we can agree to disagree, but it crosses a line when you want to comment on others' experience and put them down just because they disagree with you. No on is scoffing here, what I wrote was a considered criticism. Have a nice day.
I'm with Conal Elliot when he said on Type Theory for All that it is sooo much harder to understand a program in Python.
But to _learn_ programming, I really, really don't see how using Haskell would be simpler than Python. Perhaps if you have a specific background (e.g., math), but else python is almost pseudo code already. You'll really have to convince me that a more abstract language is better...
Python: errors based on incorrect indentation (many beginners don’t use nice IDEs), or don’t understand the meaning of the hints) and scope (don’t forget your “global” if you’re hacking in PyGame) are challenges.
Starting with 3.13, even the REPL is a sufficiently nice IDE to avoid any careless indentation errors from poor formatting (as opposed to ones caused by actually not understanding how many levels of indentation the current line of code should have).
Consider appending two strings in Scheme:
(string-append s1 s2)
Appending two strings in Scheme: (vector-append v1 v2)
Since the type is present in the function name,
it would be redundant to include it in the variable name.I have used all three languages; and you clearly have no idea of the notion of usability of a language. So many things contradict this, let me list them off the top of my head
- Getting a running toolchain working: Prexisting (most OSes bundle a Python interpreter) or a package install away for Python. Scheme / Racket is some odd mix of custom IDEs with Dr. in the name, or someone's 20 page essay on how SLIME is the best thing ever. Haskell gets into odd stuff with ghci, cabal, and stack, and all of them are extremely slow.
- Tutorials: Python has a ton of them, they all get you printing to stdout and calculating things in about 10 minutes. Scheme / Racket typically spends multiple chapters navel-gazing about lists, cons, and such. Haskell is actually better in terms of the Hello World stuff, but ghci v/s ghc bites you again; and no one has a clear idea of which one to use.
- Advanced concepts: Python has mainstream but halfhearted OOP; and things like decorators and metaprogramming. Quickly intelligible if you learned something else like Java or C++. Or if you learned shell scripts you can get quite a bit done with just imperative. Racket/Scheme: 3 chapters in and you're still trying to figure out tail recursion. Haskell: Instead of just doing fun things with take and foldl you're being hit with trivia about typeclasses.
You’re replying to a post making assertions about beginners.
That doesn’t usually mean people with 4 years programming experience picking up a new language.
Racket: criticizing for having a beginner-friendly IDE doesn’t make a lot of sense. There’s always Magic Racket for VSCode for the others.
I guess you’re not starting people with “How to Design Programs” because that’s pictures and animations for ages.
Haskell: that was funny but an absurd criticism ghc vs ghci?? Nobody has that problem. The other stuff - valid but lead with it instead of trolling.
Back when I was a beginner actually interested in getting out of the beginner step of Haskell, this was an issue for me times. So there's at least one person :)
Also anecdotally I have seen people ask this in Freenode #haskell as well (the “Freenode” probably tells you how long ago this was) ; and there a few issues [1] and [2] where I see beginners having the same/similar issue. The second one is particularly funny, 4 people give 5 solutions and no one seems to know what the actual fix is. Instead you have people arguing whether a repeated do works or not. This would never happen with Python, just saying :)
> Racket: criticizing for having a beginner-friendly IDE doesn’t make a lot of sense.
Sorry, perhaps too harsh but I don't think it's as beginner-friendly as you think. I think it would probably help if they made the design more modern and welcoming. All these details about you can rewrite entire languages in Lisp and we can't even at least get a GUI that looks like it was written after 2007?
---------
[1] https://www.reddit.com/r/haskell/comments/1kfym5s/difference...
[2] https://www.reddit.com/r/haskell/comments/18yj7i5/i_have_jus...
Can’t take credit for the quote, read it somewhere.
The whole language changed when they kicked what’s-his-name out, and it’s a tool I almost never reach for anymore, whereas 15 years ago it was my Swiss Army knife.
Could this be so that the interpreter don't inadvertently manipulate them or pass them to a function? param=None and param="" can be very different.
*hordes
for all the hate js used to get, py is at least a few magnitudes worse.
my opinion ofc. don’t get mad xD
There's a lot of annoying issues with Python, but compared to the billions of dollars and thousands of man hours that has been spent trying to fix Javascript and how horrible it still is, it's a perfectly cromulent language.
let me guess you work somewhere that hired you to bikeshed all day
Did you encounter JS first?