I'm switching to Python and actually liking it
cesarsotovalero.net
cesarsotovalero.net
API_KEY = os.environ.get("YOUTUBE_API_KEY")
CHANNEL_ID = os.environ.get("YOUTUBE_CHANNEL_ID")
if not API_KEY or not CHANNEL_ID:
print("Missing YOUTUBE_API_KEY or YOUTUBE_CHANNEL_ID.")
exit(1)
Presenting the user with "Missing X OR Y" when there's no reason that OR has to be there massively frustrates the user for the near zero benefit of having one fewer if statement. if not API_KEY:
print("Missing YOUTUBE_API_KEY.")
exit(1)
if not CHANNEL_ID:
print("Missing YOUTUBE_CHANNEL_ID.")
exit(1)
Way better user experience, 0.00001% slower dev time. if not (API_KEY := os.getenv("API_KEY")):
...
For internal tools I just let os.environ["API_KEY"] raise a KeyError. It's descriptive enough.Sometimes that's inevitable, bit noisy of the time it isn't.
exit("Missing ...")
This prints the message and exits with code 1. if not API_KEY and not CHANNEL_ID:
print("Missing both YOUTUBE_API_KEY and YOUTUBE_CHANNEL_ID.")
exit(1)
if not API_KEY:
print("Missing YOUTUBE_API_KEY.")
exit(1)
if not CHANNEL_ID:
print("Missing YOUTUBE_CHANNEL_ID.")
exit(1)
That way you don't end up fixing one just come back and be told you're also missing another requirement if not API_KEY:
sys.exit("Missing YOUTUBE_API_KEY.")
It will print message to sys.stderr and do system exit with code 1.P.S. exit() is just an alias to sys.exit(), I prefer longer form.
API_KEY = os.environ.get("YOUTUBE_API_KEY")
CHANNEL_ID = os.environ.get("YOUTUBE_CHANNEL_ID")
assert(API_KEY, "Missing YOUTUBE_API_KEY")
assert(CHANNEL_ID, "Missing CHANNEL_ID")If the user is a typical consumer using a typical consumer interface, then yes you want to handhold them a bit more.
I recommend cookiecutter for this. I have a few templates I've built with that which I use frequently:
python-lib: https://github.com/simonw/python-lib
click-app: https://github.com/simonw/click-app
datasette-plugin: https://github.com/simonw/datasette-plugin
llm-plugin: https://github.com/simonw/llm-plugin
You can run them like this:
uvx cookiecutter gh:simonw/python-libIt doesn't copy template repos, but rather creates a list of imperative steps to perform. Steps can be both manual (obtain an API key and store it here) and automatic (run 'uv init'). Markdown syntax, ruby string interpolation and bash.
It came from a deep hate for yml based configs.
I'm not a huge fan of cookiecutter on aesthetic principles, though. I think it's chosen some needlessly heavyweight dependencies for such a simple task. PyYAML comes with a big chunk of compiled C, while performance is really not going to matter and I'd rather use TOML anyway. I've yet to see a project depending on Rich that uses more than a tiny portion of it; this is no exception. More to the point, Rich brings in the much larger Pygments (for syntax highlighting; most of the bulk is actual syntax definition rules for a variety of programming languages) and you can't disable that. And honestly I'm not a fan of the whole "download other people's templates using an integrated client" model anyway; Requests and its dependencies are pretty bulky, too.
If you work at an agency or as a freelancer and you build various similar apps with similar tooling and base setup, being able to scaffold and have them all setup quickly, not having to do it manually and waste hours id important. Similarly, if you work on various small open source packages, you want the tooling to be the same, READMEs look the same, etc, a script or tool to “spit out” the basic structure can be nice.
On the other hand, if you set up the app or larger open source package and you’ll work only on that project for potentially years, setting up a project individually, organically makes a lot of sense.
And to run cookiecutter do you still use pipx, or have you switched to `uv tool install`
I use "uvx cookiecutter" myself three days.
That's kind of very optimistic evaluation - literally anything beyond "import json" will likely lead you into the abyss of virtual envs. Running something created with say Python 3.13.x on Ubuntu 22.04 or even 24.04 (LTSs) / Rocky 9 and the whole can of worms opened.
things like virtual envs + containers (docker like)/version managers become a must quickly.
Also, it’s not the 2000s any more. Using venv to isolate application installs is not very hard anymore and there have been decent package managers for a long time.
My first taste of Python was as a sysadmin, back in 2012 or so, installing a service written in Python on a server. The dependency hell, the stupid venv commands, all this absolute pain just to get a goddamn webserver running, good lord. It turned me off of Python for over a decade. Almost any time I saw it I just turned and walked away, not interested, no thanks. The times I didn't, I walked right back into that pile of bullshit and remembered why I normally avoided it. The way `brew` handles it on macOS is also immensely frustrating, breaking basic pip install commands, installing libraries as commands but in ways that make them not available to other python scripts, what a goddamn disaster.
And no, I really have no clue what I'm talking about, because as someone starting out this has been so utterly stupid and bewildering that I just move on to more productive, pleasant work with a mental note of "maybe when Python gets their shit together I'll revisit it".
However, uv has, at least for my beginner and cynical eyes, swept away most of the bullshit for me. At least superficially, in the little toy projects I am starting to do in Python (precisely because its such a nicer experience), it sweeps away most of the horrid bullshit. `uv init`, `uv add`, `uv run`. And it just works*.
I don't think this is a silly theory at all. The only possibly silly part is that containers specifically helped solve this problem just for python. Lots of other software systems built with other languages have "dependency hell."
uv is _much_ better than what came before. As someone who has only had only glancing contact with Python throughout my career (terrible experiences at jobs with conda and pip), uv feels like Python trying to actually join the 21st century of package management. It's telling that it's in Rust and clearly takes inspiration from Cargo, which itself took inspiration from Ruby and Bundler.
Every language seems to have this problem. Or else how can we explain the proliferation of AppImage, Flatpak, Snap, ... ?
It's why I like using Bottle for small Python frontends: download the file and import.
(I'm ranting based on personal experiences with IT in the past. Yes in general virtualenv is the baseline)
If you're not using dependencies, and are writing 3.x code, there's very little justification.
Minor differences between distro versions can make a big difference, and not everyone that uses a Python script knows how to use something like pyenv to manage different versions.
I consider my point as still valid with UV, what you wanted to express?
On UV specifically - say 'asdf' compiles python right on your system from official sources - means using your ssl libs for example. UV brings Python binary - I feel worried on this.
I disagree that virtual environments represent an "abyss". It takes very little effort to learn how they work [1], plus there a variety of tools that will wrap the process in various opinionated ways [2]. The environment itself is a very simple concept and requires very few moving parts; the default implementation includes some conveniences that are simply not necessary.
In particular, you don't actually need to "activate" a virtual environment; in 99% of cases you can just run Python by specifying the path to the environment's Python explicitly, and in the exceptional cases where the code is depending on environment variables being set (e.g. because it does something like `subprocess.call(['python', 'foo.py'])` to run more code in a new process, instead of checking `sys.executable` like it's supposed to, or because it explicitly checks `VIRTUAL_ENV` because it has a reason to care about activation) then you can set those environment variables yourself.
Creating a virtual environment is actually very fast. The built-in `venv` standard library module actually does it faster in my testing than the equivalent `uv` command. The slow part is bootstrapping Pip from its own wheel - but you don't need to do this [2]. You just have to tell `venv` not to, using `--without-pip`, and then you can use a separate Pip (for recent versions — almost the last 3 years now) copy cross-environment using `--python` (it's a hack, but it works if you don't have to maintain EOL versions of anything). If you need heavy-duty support, there's also the third-party `virtualenv` [3].
Much of the same tooling that manages virtual environments for you — in particular, pipx and uv, and in the hopefully near future, PAPER [4] — also does one-off script runs in a temporary virtual environment, installing dependencies described in the script itself following a new ecosystem standard [5]. Uv's caching system (and of course I am following suit) makes it very fast to re-create virtual environments with common dependencies: it has caches of unpacked wheel contents, so almost all of the work is just hard-linking file trees into the new environment.
[0]: https://stackoverflow.com/questions/76105218
[1]: https://chriswarrick.com/blog/2018/09/04/python-virtual-envi...
[2]: https://zahlman.github.io/posts/2025/01/07/python-packaging-...
[3]: https://virtualenv.pypa.io/
Activating a venv was at least sething they could relate to.
Say you want to use a specific version of python that is not available on Ubuntu.
1. Install build dependencies https://devguide.python.org/getting-started/setup-building/#...
2. Download whichever Python source version you want, https://www.python.org/downloads/source/. Extract it with tar
3. run ./configure --enable-optimizations --with-lto
4. run make -s -j [num cores]
5. sudo make altinstall
This will install that specific version without overwriting default system python.
You can then bash alias pip to python3.xx -m pip to make sure it runs the correct one.
All the libraries and any pip install executable will be installed locally to ~/.local folder under the specific python version.
Alternatively, if you work with other tools like node and want to manage different versions, you can use asdf, as it gives you per folder version selection.
virtual environments are really only useful for production code, where you want to test with specific versions and lock those down.
(I mean, except on Windows, your venvs default to symlinking the interpreter and other shared bits, so you aren't really isolating the interpreter at all, just the dependencies.)
Looking at just the first link, looks way more complicated than venv. And I'm a C++ developer, imagine someone who less experienced, or even who just isn't familiar with C toolchains.
For example here's a ~2k line Python project which is a command line finance income and expense tracker: https://github.com/nickjj/plutus/blob/main/src/plutus
It uses about a dozen stdlib modules.
About 25% of the code is using argparse to parse commands and flags. With that said, I tend to prefer code that yields more lines for the sake of clarity. For example this could technically be 1 or 2 lines but I like each parameter being on its own line.
parser_edit.add_argument(
"-s",
"--sort",
default=False,
action="store_true",
help="Sort your profile and show a diff if anything changed",
)It's so much easier to write a simple usage() function that has all help text in one big block, and Getopt with a case block. Anyone can just look at it and edit it without ever looking up documentation.
Python lets you just nest data structures without having to twist your brain. You want a tuple in a list in a dictionary value: you just write it down and can access it with a unified notation. Bam. No reference madness and thinking about contexts. It's a big part of what I typically need to get things done quickly and understand how I did it 5 years later. That has to count for something. Python is boring in that sense, Perl is fun. But that's exactly my problem with it. It's too clever for it's own good and writing it does things to your brain (well at least mine).
That was about the time I stopped using Perl for any new projects. I never wanted to go back.
perl <<'EOF'
my $object = bless sub { my ($s) = @_; if ($s eq "thingy") { return("hello world!") } else { return("goodbye world!") } };
my %hash1 = ( baz => "here we go", foo => $object );
my @array1 = ( undef, undef, undef, \%hash1 );
my %hash2 = ( bar => \@array1 );
my $reference = \%hash2;
print( $reference->{bar}->[3]->{baz}, "\n" );
print( $reference->{bar}->[3]->{foo}->('thingy') , "\n" );
print( $reference->{bar}->[3]->{foo}->('something') , "\n" );
EOF
here we go
hello world!
goodbye world!
Perl being Perl, TMTOWTDI, but you can enforce your own style using linters and formatters.I think the classic Python vs. Perl question ultimately comes down to using what you feel most comfortable with. People are different, and that's totally fine.
Perl falls apart when you need structure and even small projects. For short scripts meant to do what you would use Bash for, it's a godsend. Safer, more portable, less friction.
There seems to very few smart people who agree with this in practice, however.
Those were the days…
I got forced to learn it for a project where I was proposing Ruby and the customer insisted on Python. This was years ago when Ruby was much slower. I was annoyed but I got used to it and here I am enjoying it many years later.
I take issue with the description and use of make though! :-D What is the point of it if you're not going to use dependencies? One might as well just write a script with a case statement..... I'm adding smileys because I don't mean to be all that serious but I really do think that the failure of the youth of today to get to grips with Make is a sad reflection on our culture....... and get off my lawn, ok? :-)
I ended up feeling that it removes a lot of (internal) debate about what's the best style for braces that you get in C-like languages.
Genius.
I started with this and evolved into simple flat Makefiles, because they're basically the same but Make feels more standard (there's a Makefile here vs. there's a random bash script here).
Coming from a mathy background I found it incredibly satisfying, although I’ve come around to other languages since.
Inheritence should have stayed esoteric. Composition is closer to reality.
Relations and functions and values are way closer to applications than OOP seems to be.
Then, in 2005, Guido van Rossum was hired by Google to work on Google Cloud. That opened the door for wider adoption in academia, since Python had strong math libraries and integrated well with tools researchers were already using, like Hadoop, right around the time big data and ML were starting to take off.
Also, between 2005 and 2006, two important things happened: Ruby on Rails came out and inspired Django, which was starting to gain popularity, and web developers were getting tired of Perl's messy syntax. That's how Python quickly became a solid choice not just for server-side scripts, but for building proper web apps. In the meantime, another language that could be embedded directly into HTML was storming the web: PHP. Its syntax was similar to JavaScript, it was easy to pick up, lowered the barrier to entry for software development, worked straight out of the box, and didn't require thousands of print statements to get things done.
The 3 Ps made history. According to programmers from 20 years ago, they were like religions. Each had its own philosophy and a loyal group of followers crusading online, getting into heated debates, all trying to win over more adopters. The new generation of devs is more pragmatic. These days it's less about language wars and more about picking the right tool for the job.
It's very weird reading something you lived through described in these terms, as though it were being described by an anthropologist.
Can't help but wonder what the future will have to say about today.
"In 2025, programmers used 'frameworks' to hang 'apps' from some kind of 'web'. They believed these frameworks gave them magic powers. Many even fought in 'flame wars' on this topic, which experts believe involved the use of fire to destroy webs woven by competing programmers."
It brought across a ton of users from R and Matlab.
Pandas, Matplotlib and ScikitLearn then consolidated Python's place as the platform of choice for both academic and commercial ML.
PHP's popularity isn't really from 2005-2006. It was popular at the end of the 90s, and it looks like JS as much as it looks like a potato.
You run it by saying `python hello.py`.
Compare that to the amount of crap you need(ed) with 2005 Java just to have something running.
The shittiness of ActivePython and generally getting python to run on Windows were a bit of a hurdle, but still it was easier than the competition
No, entry-level courses were in a mix of Scheme, C, and other languages until Java’s industrial ubiquity ended up in it becoming a popular choice, but not completely displacing the others. Then as Python (along with the broader class of dynamic OO scripting languages) became quite popular, Python got added to the mix, but, unlike Java, it fairly quickly displaced not only Java but a lot of what had been around longer in introductory courses.
Python’s industrial success drove its initial use in introductory courses, but doesn't fully explain it, as it doing what Java never did indicates.
I think the people teaching introductory courses find it less of a compromise to industrial popularity than they did with Java.
Python’s success is multifaceted, some of it is right-place right-time, a lot of it is the ecosystem it built because of that, but a lot of it is, I think, that it turns out to be a language that has been designed (both initially and in how it is been managed over time) to be a very good language for real people solving real problems, despite not adhering to any of what various typing and performance and paradigm purists like to posit as the essential features of a good language.
2. Python on web servers was a thing long before 2012. You had Zope in 1998 or so, and it was pretty popular for a while, and hugely influential to subsequent web frameworks. Django came out in about 2005. TurboGears, Pylons in about 2005 or so. Flask in 2010... and these are just the more popular frameworks.
3. I think the author meant that PHP was also vaguely C-like in syntax, like JS. Keyword: vaguely. You had a common way of declaring stuff with curly braces and semi-colons.
Python had web servers from 2000, including Jim Fulton's Zope (really a full framework for a content management system) and in 2002 Remi Delon's CherryPy.
Both were useful for their day, well supported by web hosting companies, and certainly very lightweight compared to commercial Java systems that typically needed beefy Sun Solaris servers.
I think this could be generalized to ergonomics. Java 1.6 is an absolute nightmare for a newb compared to Python. No top-level statements, explicit typing, boxing, verbose declaration syntax, long import statements, curly braces everywhere... and, most importantly, no out-of-the-box REPL. Java has since made strides and borrowed from Kotlin and Lombok but my understanding is that it was too little too late.
Depending upon preference and application one might consider ~half of these things anti-features at the expense of stability but it speaks volumes to why people join and stay in a software ecosystem. If I had to work on a Python project again I'd use mise with uv and viciously lint the codebase down to a reasonable fully-typed subset of the language devoid of custom decorator magic and dunder abuse.
Yeah, after 2008. And by 2014, it had overtaken Java in many CS programs. But I was referring to the events that led to that.
Being default installed was almost certainly the larger factor. As evidenced by how much pain it caused people when they started using dependencies that were not stable. They had to add specific support for it to not let you pip install things to the system install.
Excuse me but... what ? Django is 20 years old.
Nonsense.
> I don't think I heard about web servers in Python before 2012.
More nonsense.
> I suppose a 2005 computer wouldn't be able to serve a Python backend smoothly.
Extreme nonsense.
https://medium.com/signal-v-noise/ruby-has-been-fast-enough-...
And this is when Python was edging Ruby performance wise.
I feel like the religious wars aspects of this is completely overblown. The conversations around languages really hasn't changed all that much in the last 20 years. Look at the way you see things happening in HN, heck even in the comment thread right here. It's the exact same kinds of discussions as happened back 20 years ago, with exactly the same kind of pragmatism.
I think the gulf comes about from the fact that the primary sources are conversations occurring between people that are largely anonymous behind an online handle, interacting with people that aren't face to face. There's always been an element of exaggeration in the interactions. What might be a "Umm, no" with a maybe a shake of the head, becomes "Your mother was a hamster and your father smells of elderberries". Almost every party involved comes to recognise the particular cultural quirks (which varied from forum to forum) and how to translate what was said, to what was actually meant.
Yea, I was a sysadmin around 2000 (before that too) and I knew it as such.
>between 2005 and 2006, two important things happened:
Somewhat - I used it in 2001 for Plone which is based on Zope, which was somewhat popular around that time. Writing all the web stuff with Python made sense, since Plone provided a CMS and could include a wiki. Adding on some sql calls to it in python just made sense. The competition was between PHP and Python, though there were some other less popular choices. Ruby on Rails definitely was getting a lot more popular around those times. PHP didn't start getting popular around 2005, if anything, people started using Python more, and started criticizing the crappy code that was circulating in the PHP community.
In any case, it was a fun time, but what's the point of looking back like that?
I remember saying to a coworker, "Google is single-handedly keeping Python alive."
Then bam. It was everywhere. Mid 2010's, I took a cybersec job and they told me to learn Python in the two weeks between accepting and starting. "Python is all over cybersec," I was told. It was then I realized Python took over academia, which positioned it perfectly for ML. It's features made it easy to start, but it also benefited from right place, right time.
You should talk to the Java advocates in my company :) The language wars are still around, it's just Java vs the rest now.
Apple also added Python in, I think, Mac OS X Jaguar, so it blipped on the radar.
When you have big players using it, the community automatically grows.
Django was the continuity of that and definitely contributed to the growing popularity of Python, but I think the hype started way before (and in fact we had to suffer Zope/Plone/Twisted for it :)).
Another decisive date was circa 2010 when the Belgian book "learn programming with python" came out. Granted, Django was already 5 years old at the time, but it brought many beginners who knew nothing about programming at the time.
For example, to install yt-dlp, I followed these steps:
sudo apt install pipx
pipx install yt-dlp
Actually, only the second one, because I already had pipx (https://pipx.pypa.io/ — a wrapper for pip that does basic virtual environment management) installed.Can you name some specific things in Python you have tried to use, and give more concrete descriptions of how you tried to set them up?
This interview from Brett Cannon (old core dev who worked on packaging, imports, the vscode python extension...) is eye opening:
https://www.bitecode.dev/p/brett-cannon-on-python-humans-and
The guy cares SO MUCH and they have so many things not to break you can feel the sense of passion and the weight of responsibility in everything he says.
It's like when people pick a "unique" name for their baby along with almost everyone else. What you thought was a unique name is the #2 most popular name.
What exactly is the problem with __init__ or __new__? @dataclass is very nice syntactic sugar, but are we arguing here that having access to initializer/allocator/constructor dunder methods is "legacy ugliness"? This is the core of pythonic built-in aware python. Bizarre.
Kotlin: constructor is either part of class definition or keyword constructor.
Ruby: initialize
JS: constructor
Python: ______new______, _______init_______
Literally this meme: https://knowyourmeme.com/memes/three-headed-dragon
I think it's fairly short sighted to criticize these. FWIW, I also did that the first time I wrote Python. Other languages that do similar things provide a useful transparency.
Same-other-language-user:
((()))()()()({}{}{{(((())){}}}}{}{}{};;;;();)(;}}}
*not supposed to be correct syntax, it's just a joke
IMO this is less horrendous than e.g. go's insistence that exported functions are indicated by a capital letter - that really affects code using the module not just the definition.
Ruby has the same thing but it’s called ‘new’.
Implementing the type of customization (idiomatically) that __new__ provides in Kotlin and JS isn’t any cleaner.
I wrote it in about an hour and a half. It seems to work in Python 3.6 and 3.12. Now you never need to write another double-underscore magic method again.
But who cares? It's syntax, it has its purpose.
My read was that the "ugliness" was in the method naming, and specifically the double underscores, not the availability of the methods themselves.
In C++, if you want to define the binary + operator on a class, you give the class a method with the special name `operator+`. In Python, to do the same thing, you give the class a method with the pseudo-special name `__add__`. You don't think the Python way is worse?
Have you considered how much familiarity might shape your reaction to the two? Both are specific patterns with arbitrary restrictions which new learners have to internalize, and that’s a fairly advanced task most people won’t hit until they are familiar with the language syntax.
Here’s a counter argument: the Python methods are normal method declarations whereas C++ developers have to learn that “operator=“ means something different than what “operator =“ (or any other assignment statement) would mean, and that this is the only context where you can use those reserved symbols in method names.
To be clear, I don’t think either of these is a big deal - the concepts and usage are harder to learn than the declaration syntax – but I think it’s incredibly easy to conflate familiarity with ease of acquisition for things like this.
This doesn't apply to the dunder methods, though. They're magically exempt from this magical mangling, so you could call them directly if you wanted. ¯\_(ツ)_/¯
> You don't think the Python way is worse?
They seem about equivalent? I don't see any real reason to pick one or the other, beyond personal preferences.
Take a deep breath.
Maybe as I grow to think of the "big picture" architecture-wise with my code, I will start incorporating dunders, but until then...
https://www.attrs.org/en/stable/why.html
There's some obvious potential for bias there, but I thought most of the arguments were well reasoned.
Edit: found another that I thought was helpful: https://threeofwands.com/why-i-use-attrs-instead-of-pydantic...
My rule of thumb is to use Pydantic if I need serialisation, otherwise I default to dataclasses.
I'd use it for passing flat structures as function args rather than a massive list of individual args.
It's included in the default install of most desktop/server Linux distros (with plenty of exceptions), but I don't believe any of the BSDs ship it in their base system.
IIRC macOS used to have python 2 in its default install, but I vaguely recall that being deprecated and removed at some point. My only Mac is on the other side of the country at the moment, so I can't check myself.
https://developer.apple.com/documentation/macos-release-note...
I wonder if that kerfuffle is why they ended up not removing Ruby and Perl yet, despite the same promise. macOS’ Ruby is around 2.6. Perl I somehow doubt they’ll get to, as it’s such an important part of Unix admin I bet they themselves use it somewhere.
There is still a /usr/bin/python3 which is a shim. When you call it, if you don’t have the Xcode Developer Tools you’ll be asked to install them (it’s a non-scary GUI dialog which takes two clicks) and then you’re set. That is also a few versions behind the cutting edge, but it does get updated sometimes.
I will never understand this.
But then, I've been using Python 3 since 3.2 and my first reaction to that was a sigh of relief, and by the time I updated to 3.4 I was already wondering why everyone else was lagging behind on the switch.
They also have a habit of sometimes add or change some APIs mid-cycle, so you may see requirements for the latest Xcode being a mid-cycle version of the OS. Or how was it with Mac App Store, that not only itself, but the relevant APIs for programs distributed in it appeared in 10.6.4?
Edit: Unlike older versions of macOS that came with Python 2.7 pre-installed, newer macOS versions (Catalina and later) no longer include Python pre-installed.
https://news.ycombinator.com/item?id=44580198
The removal was in Monterey. Catalina and its successor Big Sur very much still had it. Catalina was the one that removed 32-bit support.
My thoughts about python here: https://calvinlc.com/p/2025/06/10/thank-you-and-goodbye-pyth...
Next time I get into Python I’ll try uv, ruff, ty.
The rest is small stuff that adds up like Py whitespace scoping, or Py imports somehow not taking relative paths, or JS object syntax is nicer: https://news.ycombinator.com/item?id=44544029
uv + ruff (for formatting AND linting) is a killer combo, though.
And the more you use uv, the most you discover incredible stuff you can do with it that kills so many python gotchas like using it in the shebang with inline deps or the wonders of "--with":
It solves a lot of the package management headaches for me.
That said, call me old-fashioned, but I really take issue with "curl $URL | bash" as an installation method. If you're going to use an install script, inspect it first.
Two additional suggestions:
* mise to manage system dependencies, including uv version and python itself
* just instead of make; makefile syntax is just too annoying.
Mise actually has a command runner as well which I haven't tried yet, and might work better for running commands in the context of the current environment. It's pretty nice when your GitHub actions workflow is just:
* Install mise
* mise install everything else
uv venv activate
The command actually creates a new venv in a dir called "activate". The correct way to activate the venv is like this: source .venv/bin/activateOtherwise, the existence of a file or folder with the same name as your task ("test", for example) will stop that task from being run, which might be very annoying if you're using the Makefile as part of a script or CI or something where you won't notice the "Nothing to be done for..." message.
IIRC the info pages just say that it is for targets that lack a file. This is way easier to remember.
I've switched to python primarily (from perl) in early 2010s (I think my first "seriously" used version was 2.6. This is mostly for system management, monitoring, and data transformation /visualisation. Nothing fancy like AI back then in a work setting.
I found the biggest impact was not so much on writing code but on it remaining readable for a very long time, even if it was created hastily "just get this working now" style. Especially in a team.
Python is still one of my favourites and the first tool I reach if bash is not enough for what I'm trying to do.
I always found vscode lacking for Python and C compared to pycharm and clion. The latter just work without fiddling with config files.
In my experience (not saying this is universal), the folks that like JetBrains IDEs came from java/intellij backgrounds, where I hear it really shines.
This all might be a skill issue, as almost all my professional projects have been VSCode based, but since I've only worked at smaller places I definitely can't rule out this was because it was easier to set things up than to fight for Fin to get us all licences.
In your opinion, what makes PyCharm (or CLion if you want to add that in) 'just work'? Do you think it is because you've used it for so long and just know the ins-and-outs? Or is there something you see that they have and VSCode doesn't?
I've always been curious about this as someone who hasn't had a lot of professional exposure to the JetBrains world.
import re
import sys
if re.match(r"[A-Za-z}", sys.stdin):
print("ok")
PyCharm will spot that error immediately, no insaneo configuration requiredPyCharm Professional also gets into the SQL side of things:
with connect(":memory:") as conn:
c = conn.cursor()
c.execute("SELECT * FROM oops WHERE NAME IN ('alpha, 'beta')")
instantly spotted, no configuration requiredI was going to be cute and use json.loads as an example, but it seems somewhere along the way they botched the fact that the first argument to json.loads is a fucking JSON string. But, it does allow showcasing that you can have PyCharm syntax check the "inner" language of any string literal you'd like via their language injection annotations:
import json
# language=json
bogus = """{"this is subtly wrong"; true}"""
json.loads(bogus)
# and the same with tomllib:
import tomllib
# language=toml
bogus = """
[alpha]
beta = []onoz
"""
tomllib.loads(bogus)
# or, if you have some tricky internal stuff
def do_the_thing(
# language=sql
s: str
):
with connect(something) as conn:
c = conn.cursor()
c.execute(s)
do_the_thing("SELECT * FROM oops WHERE NAME IN ('alpha, 'beta')")
# ^^^ also gets syntax checked because it knows the first arg is SQLIf you want to read binary files, you can use C or python, but not go (unless you use segfault prone 3rd party libraries of very dubious quality, of course).
Make is great at compiling code in languages that don't have bespoke build systems. If you want to compile a bunch of C, awesome. For building a Rust or JavaScript project, no way. Those have better tooling of their own.
So for the last 15 years or so, I've used make as a task runner (like "make test" shelling out to "cargo test", or "make build" calling "cargo build", etc.). As a task runner... it kinda sucks. Of course it's perfectly capable of running anything a shell script can run, but it was designed for compiling large software projects and has a lot of implicit structure around doing that.
Just doesn't try to be a build system. It's optimized for running tasks. Here, that means it provides a whole lot of convenient functions for path manipulation and other common scripty things. It also adds dozens of quality-of-life features that devs might not even realize they wanted.
For example, consider this trivial justfile:
# Delete old docs
clean:
rm -rf public
# This takes arguments
hello name:
@echo Hello, {{name}}
If you're in a directory with it and run `just --list`, it'll show you the list of targets in that file: $ just --list
Available recipes:
clean # Delete old docs
hello name # This takes arguments
That second recipe takes a required command line argument: $ just hello
error: Recipe `hello` got 0 arguments but takes 1
usage:
just hello name
$ just hello underdeserver
Hello, underdeserver
You can do these things in make! I've seen it! Just doesn't add things that were impossible before. But I guarantee you it's a lot harder to implement them in make than it is in just, where they're happy native features.There are a zillion little niceties like this. Just doesn't try to do everything make does. It just concentrates on the smaller subset of things you'd put in .PHONY targets, and makes them really, really ergonomic to use.
You wouldn't use just to replace make in a large, complicated build. I would unhesitatingly recommend it for wrapping common targets in repos of newer languages, so that `just clean build test` does the same things whether you're in Python or TS or Rust or whatever, and you don't want to hack around all of make's quirks just to build a few simple entry points.
The benefit of just is that it's designed to be a command runner, whereas make is designed to be a build tool. justfile syntax is much simpler and more ergonomic. It also has nice features: Private recipes, submodules, recipes that you can specify to run only in a particular OS (we use the same justfile for both Windows and Linux), writing your recipes in a language other than your shell language, and many many other niceties.
A new user can start doing "advanced" stuff in just in a couple of hours. They'll take a lot longer if trying to do it via make.
Is this referring at all to to PyTorch. If not, any guesses what the author has in mind
"Not only because the syntax is more human-friendly, but also because the Python interpreter is natively integrated in all Unix distros."
Is this referring to GNU/Linux.
UNIX (UNIX-like) includes more than Linux; some UNIX distributions do not include Python in the base system
Where it is left as choice to the user whether to install it
I know this because I use such distributions and, unless some software needs it, I do not install Python
In such case, when I am done using that software I uninstall it^1
For example, he mentions retrieving YouTube channel metadata
I do not use Python for this; I use a 19-line shell script (ash not bash), its startup time is faster
Unlike Python, it is included in the base system of the UNIX distributions (both Linux and BSD) that I use
But if I need to test something using yt-dlp, then I might temporarily install Python
1. I compile Python from source and one annoying aspect of the project , in addition to the slow startup time, is their failure to include an uninstall target in their Makefile
He's referring to Python in general
"Not only because the syntax is more human-friendly, but also because the Python interpreter is natively integrated in all Unix distros."
I think he means that its available or readily available in many major linux distributions like Ubuntu, Fedora, NixOS, etc. I don't think native is the right word.
I use bash too but Python is amazing. You're right, there are problems related to packaging and speed, but it is still very often the right tool for the job. It's powerful, easy to use, and open source.
Ash doesn't do web requests unless you've implemented HTTP in ash. You're back to using 3rd party dependencies that aren't installed on all systems
I asked Gemini code to generate the same skeleton stuff I was coding in nodejs, and did it perfectly in 1/8 of the code required. python's been maintained for years and is honestly a really easy syntax.
for OS level things, python makes sense and is the defacto. right now I vibe code and skeletons and then determine what I want to add and add it manually.
I'm enjoying the switch, maybe not as hardcore of a dev as many of you but it gives me the fix of having an engineering mind and being able to test things on the fly since I live in a terminal anyways.
I agree with all the points. I do like however that I can do:
def functionHere(something: str, someNumberOrSomething: int)
Seeing all the "uv has made me want to use python again" posts got me in to it. glad I did.
I get that it's not the shiny new thing, but I don't understand people hating on it. Is this just junior devs who never learned it, or is there some new language out that I missed? (And please don't tell me Javascript....)
I mostly just don’t like some of the design decisions it made. I don’t like that lambdas can’t span multiple lines, I don’t like how slow loops are, I don’t like some functions seem to mutate lists and others don’t, and I am sure that there are other things I missed.
But it really comes down to the fact that my career hasn’t used it much. I occasionally have had to jump into it because of scripting stuff, and I even did teach it for a programming 101 course at a university, but I haven’t had a lot of exposure otherwise.
For scripting stuff for myself, I usually end up using Clojure with GraalVM (yes I know about babashka), or nowadays even just a static linked compiled language like Rust.
I don’t really understand why people think that compiled languages can’t be used for scripting (or at least task automation), honestly. Yes you have to add a compilation step. This involves maybe one extra step during development, but realistically not even that. With Rust I just do cargo run while developing, I don’t see how that’s harder than typing Python.
- Environment / dependency management
- Type safety
- Performance
As the author points out, these have largely been addressed now by uv, type hints, pydantic, FastAPI, etc.
Backward compatibility, which I suppose is closely related to needing to use env, is also a pain. In my experience you can't go forward or backward in many cases. It's especially painful on projects that don't change very often. I'm sure that an active codebase can probably be kept updated from version to version, but if you've waited a bunch of versions, it seems painful.
But, I'm not sure I've given it a fair shake because I haven't needed to. It's use in AI does make it an attractive option, now more than ever.
- statically typed languages got better, reducing the relative benefits of dynamic typing. people realized they didn't really hate static types, they hated the way [insert 90s-00s enterprise language here] did static types
- the GIL became more and more painful as computers got more cores
- it used to be a language for passionate hackers, like a latter-day lisp (cf that old pg essay). "written in python" used to be a signal of quality and craftsmanship. now it's the most commonly taught beginner language; millions of bootcamp devs put it in their CV. average skill level plunged.
- PSF was asleep at the wheel on the packaging / tooling problems for years. pip/venv are dinosaurs compared to cargo, nix, or even npm.
Seems more like it's fallen out of favor with senior devs who have moved to Go/Rust.
It doesn’t ship with a first party package manager so you got the community trying to fill this gap. Use any other language with good tooling like golang or rust and it is a breath of fresh air.
Python used as an actual PL is a footgun because it’s dynamic scripted. (Don’t tell me about using tools X, Y, Z, mypy, …) You essentially become the compiler checking types, solving basic syntax errors when uncommon paths are executed.
A programming language that’s only good for < 100 line scripts is not a good choice.
I honestly wish python were in a better state. I switched to rust.
It's still reigning champion of data science, and of course it has a huge number of uses and users still around, but it's not really cool or in vogue outside of those circles.
Plus there has been a rising sentiment against dynamic typing by masochists over the last decade or so.
But then uv came along. It's not just another poetry or pipenv or whatever. It works well and it has uvx and `uv tool install` and other nice things.
Previously when I saw that something was written in Python I'd curse under my breath because I don't have the mental energy to make a virtual environment and source its shell script and remember where I put it just to try something out. On arch Linux I can't just pip install something without going through some crazy setup that I used to have patience for but as I get older it's been better to just curse that snake language altogether and spend time doing something more chill like writing rust.
But now I can just type "uvx" and it works. I'm probably not going to start writing python again any time soon, but at least now I have less reason to be disappointed with people who themselves choose to write Python.
Everyone will mention uv/pyenv/poetry/conda/virtualenvs, so fine, let's pretend it's not a problem that you tried each of those in desperation and they are all household names. Suppose the packaging wars actually ended and everyone uses what you use without you needing to tell them, and suppose further that every pypa problem isn't blaming debian maintainers for obvious regressions. Pypi will still yank[0] packages at the source to perhaps randomly break deterministic behaviour, smashing anything in the blast radius rather than only clients using a --strict flag or something. You can pin your own dependencies but who knows what they will pin (nothing, or the wrong stuff, or the wrong way probably!) or what they will yank. Now for repeatability you need to host a mirror for everything you use- which is fine for corporate but a nonstarter for the novice and annoying for FOSS projects.
If you have zero dependencies or a slowly changing environment that is receiving constant care and feeding, you'll probably never notice how bad things are. If you put down most projects for a month though and pick them back up, perhaps with a different environment, machine, or slightly different python version it's broken, bitrotted, and needs serious attention to be rehabilitated.
People might argue.. that's just software dev. Nope. I say it with lots of love and gratitude for the efforts of the community.. but most langs/ecosystems would never tolerate this level of instability. One has to eventually just admit that working, portable, and actually repeatable environments with python basically just require docker. Can we talk about how "--break-system-packages" is hilarious? After you retreat to docker you can type this a few times a day, push the container up, pull it down months/years later, and then realize that literally the only way to get a stable working environment is to request a broken one. QED
Of course then crypto bros happened and the rest is history.
Worked at a company where that approach lead to huge unwieldy structure that no one dared to touch to not break anything other teams are working on. The problem was not so much the repo, but dependencies structure (like single requirements.txt for the whole repo) and build scripts.
In theory it should've worked great -- you only need to update a dependency once and be sure all the code has the most recent security patches. In reality everyone was afraid to touch it, because it will break someone's else code. Monorepos work great only if you have serious NIH syndrome (Google).
I actually started appreciating damned micro-services there, as long as each service just reflects organization team structure.
That said, I think service-per-team is a good pattern
1: I don't like dealing with language crossing boundaries with it - it's painful, especially if to/from a compiled language - there's just too much friction. It's easy to write but hard to maintain.
2: Debugging python can be...painful. If you have a pure perfect environment dedicated to pure python and all the tooling set up, it can be breezy. But if you have something messier like C++ that calls Python or vice-versa and are using an editor not made for Python like QTCreator then suddenly it becomes hell.
Who owns this data!? Where was this initialized!? What calls this function!? What type is this data!?!?!?!? These questions are so effortless in C++ for me and so very painful in Python. It slows efforts to a crawl.
It feels exhausting to have to go back to print statements, grep, and hand-drawing a graph that shows what functions call what just to figure out WTF went wrong. It's painful to a degree that I avoid it as much as possible.
...and don't even get me started on dealing with dependency problems in deployments...
I've never found myself debugging the underlying C++ code unless developing a C++ extension. But is it really that hard? Just point lldb to the python process and run your script in lldb.
If the C++ is not yours & assuming it's a mature lib (e.g. PyTorch): it's probably an error caused by you in Python land.
But Python's tooling, particularly with what Astral is doing (uv, ruff, ty), is so good, I'm always using Python and virtually never using Ruby. And yeah, the rich libraries, too.
I thought nodejs/typescript seemed to be the default that most LLMs choose? Or is that just v0/lovable/replit? (although replit seems better about going non-js sometimes)
Think JNI or cgo management.
It's just HN users are more likely to be that somebody else. Probably we have to deal with non-python dependencies anyway so we're reaching for bigger hammers like docker or nix. It would be nice if there wasn't a mess of python package managers, but whichever one I use ends up being a somewhat unimportant middle-layer anyway.
And it seems like the package resolution is finally local by default, although that requires a 'virtualenv', which seems to be a legacy of the global packaging system.
I find this depressing. Not only are LLMs covertly reducing our ability to think and make decisions, they’re now also making people voluntarily conform to some lower common denominator.
It’s like humanity decided to stagnate at this one point in time (and what a bad choice of point it was) and stop exploring other directions. Only what the LLM vomits is valid.
I have been enjoying Lisp languages since the late 1970s, and today it makes me happy using Common Lisp and Racket in the same way as when I stood in a forest very early this morning drinking coffee makes me happy.
But, Python is also a fun language and phrases like “good enough” and “actually liking it” are valid.
There is nothing more annoying than tons of little repos all of which containing tiny projects with a few hundred lines of code but (of course) you need most / all of them to do anything. Use a mono repo until there is some obvious reason to split it up imo.
On the flip side, we have an org with 50+ teams and our operations team is pinning for a monorepo. They are just fine with one team's push forcing N teams to have an unexpected deploy and recycling of caches, connections, etc. Not to mention what will happen when team A doesn't have time to deal with team B's merge due to other org pressures.
For personal projects, though, I get the value of an actual small projects monorepo.
But weiting a processing pipeline with Python is frustrating if you have worked with C# concurrency.
I figured the best option is Celery and you cannot do it without an external broker. Celery is a mess. I really hate it.
I'm hoping the existence of free-threading will push for more first-class concurrency primitives. Concurrent Futures is nice until you need a concurrent-safe data structure besides a queue
I also had a lot of problem due to async primitives with sqlalchemy - there's some tricky stuff with asyncio.gather vs TaskGroup and how sqlalchemy session works with it to be able to compose code easily.
e.g. rather than:
> It’s important not to do any heavy data processing steps in the project-ui … we keep the browser application light while delegating the heavy lifting and business logic to the server
Chomp the complexity, serve HTML from the backend directly > ty
Im curious where ty goes but for a min-complexity stack i couldnt spend complexity tokens on pre release tools > pydantic … dataclasses
One or the t’other, plus i’ll forever confuse myself: is it post_init (dataclasses) or is it post_model_init (pydantic) - i had to check! > docker
if we already have uv, could we get away without docker? uv sync can give an experience almost akin to static compiled binaries with the right setup. Its not going to handle volumes etc so if you're using docker features, this concept isnt going to fly. If you're not wedded to docker though, can you get away with just uv in dev and prod? in an enterprise prob not, i wouldn't expect to be able to download deps in prod. For flying solo though… > compose
You’ve a frontend, a backend and presumably a database. Could you get away with just uv to manage your backend and a local sqlite db?So a broadly feature comparable stack for rapid iteration with less complexity but still all the bells and whistles so you dont need to cook everything yourself, might look like:
- uv
- fastapi + jinja + htmx + surreal + picocss
- sqlite
You could probably sketch a path to hyper scale if you ever needed it: - v1 = the above stack
- v2 = swap sqlite for postgres, now you unlocked multiple writers and horizontal scaling, maybe py-pglite for test envs so you can defer test-containers adoption for one more scaling iteration. WAL streaming would add some complexity to this step but worth it
- v3 = introduce containers, a container registry and test-containers. I dont think you really unlock much in this step for all that additional complexity though…
- v4 = rke2 single node cluster, no load balancer needed yet
- v5 = scale to triple node, we need a load balancer too
- v6 = add agent nodes to the rke cluster
- v7 = can you optimise costs, maybe rewrite unchanging parts in a more resource efficient stack
…"I went from being fully covered in mud to only being half covered in mud, and it's great! I don't understand why people complain about being half covered in mud."
More seriously: it's fascinating and interesting to see how closely this article mirrors my own Python project layout. The tools and practices have come a long way over the last decade and good developer standards have a way of becoming the defacto in organic fashion.
IMO uv has won the race to be the new standard for Python environment management (Poetry gave it a solid try, but lost on speed).
Why is that?
Why Python for AI?
That said, I remember writing myself a note a few years ago to avoid Python projects. I had to clean up code from all over the company and make it ready for production. Everyone had their own Python version, dependencies missing from requirements.txt, three way conflicts between 2 dependencies and the python version, wildly different styles, and a habit of pulling in as many libraries as possible [1]. Even recalling those memories makes my stomach turn.
I believe constraints make a project shine and be maintainable. I'd prefer if you throw at me a real python instead of a python project.
[1] Yes, I'm aware of containers, I was the unlucky guy writing them.
Still could be better, but I think Python's really hit its stride now.
In my personal timeline, people giving up waiting for Perl 6 were a huge source of early Python developers.
Made me think this is probably normally a Ruby developer indoctrinated against Python. The article doesn’t seem to say what they have come from.
Ruby devs think about code differently. Like Perl, they embrace TIMTOWTDI.
https://perl.fandom.com/wiki/TIMTOWTDI
Also, there's a pride in writing elegant code as opposed to following "Pythonic" conventions. Excellence is not conformity.
I use Python a lot more often now because it's seen as simpler and more approachable and easier to force compliance. I miss Ruby.
> If you know me, you know I used to be mostly a Java/JavaScript/R kind of guy. ↩