Back

Go away Python

408 points1 monthlorentz.app
hamishwhc1 month ago

The author’s point about “not caring about pip vs poetry vs uv” is missing that uv directly supports this use case, including PyPI dependencies, and all you need is uv and your preferred Python version installed: https://docs.astral.sh/uv/guides/scripts/#using-a-shebang-to...

meander_water1 month ago

Actually you can go one better:

  #!/usr/bin/env -S uv run --python 3.14 --script
Then you don't even need python installed. uv will install the version of python you specified and run the command.
rikafurude211 month ago

alternatively, uv lets you do this:

  #!/usr/bin/env -S uv run --script
  #
  # /// script
  # requires-python = ">=3.12"
  # dependencies = ["foo"]
  # ///
semi-extrinsic1 month ago

The /// script block is actually specified in PEP 723 and supported by several other tools apart from uv.

+1
nemosaltat1 month ago
+1
yjftsjthsd-h1 month ago
nemosaltat1 month ago

I’ve started migrating all of my ~15 years of one-off python scripts to have this front matter. Right now, I just update when/if I use them. I keep thinking if were handier with grep/sed/regex etc, I’d try to programmatically update .pys system-wide. But, many aren’t git tracked/version controlled, just laying in whatever dir they service(d). I’ve several times started a “python script dashboard” or “hacky tools coordinator” but stop when I remember most of these are unrelated (to each-other) and un/rarely used. I keep watching the chatter and thinking this is probably an easy task for codex, or some other agent but these pys are “mine” (and I knew^ how they worked when I wrote^ them) and also, they’re scattered and there’s no way I’m turning an agent loose on my file system.

^mostly, some defs might have StackOverflow copy/pasta

giancarlostoro1 month ago

You could run ripgrep on your file system root to find most of them, its insanely fast, then feed it to claude or something to generate a script to do it for you.

ncouture1 month ago

This is an awesome features for quick development.

I'm sure the documentation of this featureset highlights what I'm about to say but if you're attracted to the simplicity of writing Python projects who are initialized using this method, do not use this code in staging/prod.

If you don't see why this is not production friendly it's for the simple a good.reaaon that creating deployable artifacts packaging a project or a dependency of a project this uses this method, creating reproducible builds becomes impossible.

This will also lead to builds that pass your CI but fail to run in their destination environment and vice versa due to the fact that they download heir dependencies on the fly.

There may be workarounds and I know nothing of this feature so investigate yourself if you must.

My two cents.

zahlman1 month ago

This isn't really "alternatively"; it's pointing out that in addition to the shebang you can add a PEP 723 dependency specification that `uv run` (like pipx, and some other tools) can take into account.

rjzzleep1 month ago

I'm actually a bit annoyed that uv won. I found pdm to be a really nice solution that fixed a lot of the issues poetry had without the hard ideological stance behind it, while fixing most of its problems. But maybe that ideology was partly what drove it's adoption.

satvikpendem1 month ago

Rust is getting this feature too, it's great for one off scripts

dietr1ch1 month ago

Yeah, but you need `uv`. If we are reaching out for tools that might not be around, then you can also depend on nix-shell,

    #! /usr/bin/env nix-shell
    #! nix-shell -i python3 --packages python3
mystifyingpoi1 month ago

Yeah, but you need Nix. If we are reaching out for tools that might not be around, then you can also depend on `curl | sudo bash` to install Nix when not present.

(this is a joke btw)

+2
jonhohle1 month ago
m3at1 month ago

As shared in a sibling comment, you can get away with just curl+shell: https://paulw.tokyo/standalone-python-script-with-uv/

kokada1 month ago

The issue I have with `nix-shell` is that the evaluation time is long, so if you need to run the script repeatedly it may take a long time. `nix shell` at least fix this issue by caching evaluations, but I think uv is still faster.

Cyph0n1 month ago

This comes with the added benefit that your environment is reverted as soon as you exit the Nix shell.

+1
fastasucan1 month ago
jonhohle1 month ago

That shebang will work on GNU link based systems, but might not work elsewhere. I know that’s the most popular target, but not working on macOS, BSDs, or even busybox.

rented_mule1 month ago

I just tried the one you are replying to and it worked great on macOS. I frequently use a variant of this on my Mac.

+1
jonhohle1 month ago
m3at1 month ago

And with some small shebang trick, you don't even need to have uv installed [1], just curl and a posix shell

[1] https://paulw.tokyo/standalone-python-script-with-uv/

Supermancho1 month ago

> Then you don't even need python installed. uv will install the version of python you specified and run the command

What you meant was, "you don't need python pre-installed". This does not solve the problem of not wanting to have (or limited from having) python installed.

benrutter1 month ago

I thought that too, but I think the tricky bit is if you're a non-python user, this isn't yet obvious.

If you've never used Clojure and start a Clojure project, you will almost definitely find advice telling you to use Leiningen.

For Python, if you search online you might find someone saying to use uv, but also potentially venv, poetry or hatch. I definitely think uv is taking over, but its not yet ubiquitous.

Ironically, I actually had a similar thing installing Go the other day. I'd never used Go before, and installed it using apt only to find that version was too old and I'd done it wrong.

Although in that case, it was a much quicker resolution than I think anyone fighting with virtual environments would have.

idoubtit1 month ago

That's my experience. I'm not a Python developer, and installing Python programs has been a mess for decades, so I'd rather stay away from the language than try another new tool.

Over the years, I've used setup.py, pip, pipenv (which kept crashing though it was an official recommendation), manual venv+pip (or virtualenv? I vaguely remember there were 2 similar tools and none was part of a minimal Python install). Does uv work in all of these cases? The uv doc pointed out by the GP is vague about legacy projects, though I've just skimmed through the long page.

IIRC, Python tools didn't share their data across projects, so they could build the same heavy dependencies multiple times. I've also seen projects with incomplete dependencies (installed through Conda, IIRC) which were a major pain to get working. For many years, the only simple and sane way to run some Python code was in a Docker image, which has its own drawbacks.

lexicality1 month ago

> Does uv work in all of these cases?

Yes. The goal of uv is to defuck the python ecosystem and they're doing a very good job at it so far.

+4
prox1 month ago
+4
aeurielesn1 month ago
simonw1 month ago

> IIRC, Python tools didn't share their data across projects, so they could build the same heavy dependencies multiple times.

One of the neatest features of uv is that it uses clever symlinking tricks so if you have a dozen different Python environments all with the same dependency there's only one copy of that dependency on disk.

zahlman1 month ago

Hard links, in fact. It's not hard to do, just (the Rust equivalent of) `os.link` in place of `os.copy` pretty much. The actually clever part is that the package cache actually contains files that can be used this way, instead of just having wheels and unpacking them from scratch each time.

For pip to do this, first it would have to organize its cache in a sensible manner, such that it could work as an actual download cache. Currently it is an HTTP cache (except for locally-built wheels), where it uses a vendored third-party library to simulate the connection to files.pythonhosted.org (in the common PyPI case). But it still needs to connect to pypi.org to figure out the URI that the third-party library will simulate accessing.

runjake1 month ago

I would not be putting up with Python if not for uv. It’s that good.

Before uv came along I was starting to write stuff in Go that I’d normally write in Python.

+1
QuercusMax1 month ago
kristianbrigman1 month ago

That's partly because python has a very large installed base, and ease of entry (including distribution). This leads to people running into issues quicker, and many alternative solutions.

Unlike something like Rust, which has much fewer users (though growing) and requires PhDs in Compiler Imprecation and Lexical Exegetics.

Or C++ which has a much larger installed base but also no standard distribution method at all, and an honorary degree in Dorsal Artillery.

whimsicalism1 month ago

uv solved it, it’s safe to come back now.

regularfry1 month ago

There's definitely a philosophical shift that you can observe happening over the last 12-15 years or so, where at the start you have the interpreter as the centre of the world and at the end there's an ecosystem management tool that you use to give yourself an interpreter (and virtual environments, and so on) per project.

I think this properly kicked off with RVM, which needed to come into existence because you had this situation where the Ruby interpreter was going through incompatible changes, the versions on popular distributions were lagging, and Rails, the main reason people were turning to Ruby, was relatively militant about which interpreter versions it would support. Also, building the interpreter such that it would successfully run Rails wasn't trivial. Not that hard, but enough that a convenience wrapper mattered. So you had a whole generation of web devs coming up in an environment where the core language wasn't the first touchpoint, and there wasn't an assumption that you could (or should) rely on what you could apt-get install on the base OS.

This is broadly an extremely good thing.

But the critical thing that RVM did was that it broke the circular dependency at the core of the problem: it didn't itself depend on having a working ruby interpreter. Prior to that you could observe a sort of sniffiness about tools for a language which weren't implemented in that language, but RVM solved enough of the pain that it barged straight past that.

Then you had similar tools popping up in other languages - nvm and leiningen are the first that spring to mind, but I'd also throw (for instance) asdf into the mix here - where the executable that you call to set up your environment has a '#!/bin/bash' shebang line.

Go has sidestepped most of this because of three things: 1) rigorous backwards compatibility; 2) the simplest possible installation onramp; 3) being timed with the above timeline so that having a pre-existing `go` binary provided by your OS is unlikely unless you install it yourself. And none of those are true of Python. The backwards compatibility breaks in this period are legendary, you almost always do have a pre-existing Python to confuse things, and installing a new python without breaking that pre-existing Python, which your OS itself depends on, is a risk. Add to that the sniffiness I mentioned (which you can still see today on `uv` threads) and you've got a situation where Python is catching up to what other languages managed a decade ago.

Again.

bee_rider1 month ago

It is sort of funny, if we squint just the wrong way, “ecosystem management tool first, then think about interpreters” starts to look a lot like… a package manager, haha.

zahlman1 month ago

> you might find someone saying to use uv, but also potentially venv, poetry or hatch.

This is sort of like saying "You might find someone saying to drive a Ford, but also potentially internal combustion engine, Nissan or Hyundai".

evilduck1 month ago

Only to those already steeped in Python. To an outsider they're all equally arbitrary non-descriptive words and there's not even obvious proper noun capitalization to tell apart a component from a tool brand.

+1
zahlman1 month ago
fwip1 month ago

I imagine by this they meant `python -m venv` specifically, using that interface directly, rather than through another wrapper CLI tool.

benrutter1 month ago

Yes, that's exactly what I meant! Sorry if it wasn't clear. In my experience this used to be easily the most popular method up until a couple years ago.

zahlman1 month ago

Fair.

The way I teach, I would start there; then you always have it as a fallback, and understand the system better.

I generally sort users into aspirants who really should learn those things (and will benefit from it), vs. complete end users who just want the code to run (for whom the developer should be expected to provide, if they expect to gain such a following).

houzi1 month ago

Do you think a non-python user would piece it together if the shebang line reveals what tool to use?

benrutter1 month ago

I think yes if that line was UV. But otherwise, of its just python, you have the issue that you need two tools, one for running scripts and one for managing dependencies and environments.

MarsIronPI1 month ago

> If you've never used Clojure and start a Clojure project, you will almost definitely find advice telling you to use Leiningen.

I thought the current best practice for Clojure was to use the shiny new built-in tooling? deps.edn or something like that?

fulafel1 month ago

Clojure CLI (aka deps.edn) came out in 2018 and in the survey "how do you manage your dependencies?" question crossed 50% usage in early 2020. So for 6-8 years now.

benrutter1 month ago

Ah woops! Sorry for my outdated clojure knowledge, this sounds cool though- I'll give it a go!

codemonkey-zeta1 month ago

deps.edn is becoming the default choice, yes. I interpreted the parent comment as saying "you will see advice to use leiningen (even though newer solutions exist, simply because it _was_ the default choice when the articles were written)"

NeutralCrane1 month ago

uv has been around for less than two years. It’s on track to become the default choice, it’s just a matter of time.

the__alchemist1 month ago

I solved this in 2019 with PyFlow, but no one used it, so I lost interest. It's an OSS tool written in rust that automatically and transparently manages python versions and venvs. You just setup a `pyproject.toml`, run `pyflow main.py` etc, and it just works. Installs and locks dependencies like Cargo, installs and runs the correct Python version for the project etc.

At the time, Poetry and Pipenv were the popular tools, but I found they were not sufficient; they did a good job abstracting dependencies, but not venvs and Python version.

greensh1 month ago

sounds awesome. Just out of interest, why do you think pyflow didn't catch on, but UV did?

the__alchemist1 month ago

My best guess: I'm bad at marketing, and gave up too soon. The feedback I received was generally "Why would I use this when Pip, Pipenv and Poetry work fine?". To me they didn't; they were a hassle due to not handling venvs and Py versions, but I didn't find many people to also have had the same problem.

greensh1 month ago

thanks for sharing. Marketing seems frustrating to me for an open source project. I had similar issues with python in the past and i wish I knew about this project back then.

the_mitsuhiko1 month ago

Polish and that uv gets you entire python interpreters automatically without having to compile or manually install them.

That in retrospective was what made rye temporarily attractive and popular.

embedding-shape1 month ago

I've moved over mostly to uv too, using `uv pip` when needed but mostly sticking with `uv add`. But as soon as you start using `uv pip` you end up with all the drawbacks of `uv pip`, namely that whatever you pass after can affect earlier dependency resolutions too. Running `uv pip install dep-a` and then `... dep-b` isn't the same as `... dep-b` first and then `... dep-a`, or the same as `uv pip install dep-a dep-b` which coming from an environment that does proper dependency resolution and have workspaces, can be really confusing.

This is more of a pip issue than uv though, and `uv pip` is still preferable in my mind, but seems Python package management will forever be a mess, not even the bandaid uv can fix things like these.

sieep1 month ago

Ive been away from python for awhile now, I was under the impression uv was somehow solving this dependency hell. Whats the benefit of using uv/pip together? Speed?

embedding-shape1 month ago

As far as I can tell, `pip` by itself still doesn't even do something basic as resolving the dependency tree first, then download all the packages in parallel, as an basic example. The `uv pip` shim does.

And regardless if you use only uv, or pip-via-uv, or straight up pip, dependencies you install later steps over dependencies you installed earlier, and no tool so far seems to try to solve this, which leads me to conclude it's a Python problem, not a package manager problem.

chuckadams1 month ago

`uv pip` is still uv, it's just uv's compatibility layer for pip.

micik1 month ago

i found uv frustrating. i dont know what problem is it trying to solve. it's not a tool for managing virtualenvs, but it does them as well. i guess it's a tool for dependency management. the "uv tool" stuff. kinda weird. i gave it an honest try but i was working around it with shell functions all the time.

in the end i went back to good old virtualenvwrapper.sh and setting PYTHONPATH. full control over what goes into the venv and how. i guess people like writing new tools. i can understand that.

embedding-shape1 month ago

Maybe I "entered" the Python ecosystem at a different time, but I never used virtualenvwrapper.sh nor sat PYTHONPATH manually ever. When I first came into contact with Python, I think doing `virtuelenv venv && source venv/bin/activate` was what was recommended to me at the time. Eventually I used `python -m venv` but always also with `pip` and a `requirements.txt`. I pretty much stuck with that until maybe 1 year ago I started playing around with `uv`, and for me, I just use `uv venv|pip|init|add` from uv, and nothing else from any other tools, and generally do pretty basic stuff.

Maybe for more complex projects and use cases it's harder, but it's a lot faster than just pip and pyproject.toml is a lot nicer to manage than `requirements.txt`, so that's two easy enough wins for me to move over.

otterley1 month ago

uv solves many problems, but one textbook case is the problem of running some arbitrary Python-based command-line tool, where 1/you don’t have any Python interpreter installed, 2/your OS-provided Python interpreter isn’t compatible with the tool, or 3/you want to run single or multiple tools from any arbitrary folder where your data already is, as opposed to adapting your workflow to fit the virtualenv or running the risk that two tools have conflicting dependencies that would make the virtualenv not work well.

+1
Narushia1 month ago
JodieBenitez1 month ago

you don't even need you prefered python version, uv will download it.

zahlman1 month ago

There are really so many things about this point that I don't get.

First off, in my mind the kinds of things that are "scripts" don't have dependencies outside the standard library, or if they do are highly specific to my own needs on my own system. (It's also notable that one of the advantages the author cites for Go in this niche is a standard library that avoids the need for dependencies in quick scripts! Is this not one of Python's major selling points since day 1?)

Second, even if you have dependencies you don't have to learn differences between these tools. You can pick one and use it.

Third, virtual environments are literally just a place on disk for those dependencies to be installed, that contains a config file and some stubs that are automatically set up by a one-liner provided by the standard library. You don't need to go into them and inspect anything if you don't want to. You don't need to use the activation script; you can just specify the venv's executable instead if you prefer. None of it is conceptually difficult.

Fourth, sharing an environment for these quick scripts actually just works fine an awful lot of the time. I got away with it for years before proper organization became second nature, and I would usually still be fine with it (except that having an isolated environment for the current project is the easiest way to be sure that I've correctly listed its dependencies). In my experience it's just not a thing for your quick throwaway scripts to be dependent on incompatible Numpy versions or whatever.

... And really, to avoid ever having to think about the dependencies you provide dynamically, you're going to switch to a compiled language? If it were such a good idea, nobody would have thought of making languages like Python in the first place.

And uh...

> As long as the receiving end has the latest version of go, the script will run on any OS for tens of years in the future. Anyone who's ever tried to get python working on different systems knows what a steep annoying curve it is.

The pseudo-shebang trick here isn't going to work on Windows any more than a conventional one is. And no, when I switched from Windows to Linux, getting my Python stuff to work was not a "steep annoying curve" at all. It came more or less automatically with acclimating to Linux in general.

(I guess referring to ".pyproject" instead of the actually-meaningful `pyproject.toml` is just part of the trolling.)

kstrauser1 month ago

> Third, virtual environments are literally just a place on disk for those dependencies

I had a recent conversation with a colleague. I said how nice it is using uv now. They said they were glad because they hated messing with virtualenvs so much that preferred TypeScript now. I asked them what node_modules is, they paused for a moment, and replied “point taken”.

Uv still uses venvs because it’s the official way Python stores all the project packages in one place. Node/npm, Go/go, and Rust/cargo all do similar things, but I only really here people grousing about Python’s version, which as you say, you can totally ignore and never ever look at.

zahlman1 month ago

From my experience, it seems like a lot of the grousing is from people who don't like the "activation script" workflow and mistakenly think it's mandatory. Though I've also seen aesthetic objections to the environment actually having internal structure rather than just being another `site-packages` folder (okay; and what are the rules for telling Python to use it?)

The very long discussion (https://discuss.python.org/t/pep-582-python-local-packages-d...) of PEP 582 (https://peps.python.org/pep-0582/ ; the "__pypackages__" folder proposal) seems relevant here.

+1
kstrauser1 month ago
tgv1 month ago

Won't those dependencies then be global? With potential conflicts as a result?

auxym1 month ago

uv uses a global cache but hardlinks the dependencies for your script into a temp venv that is only for your script, so its still pretty fast.

stephenlf1 month ago

Nope! uv takes care of that. uv is a work of art.

tgv1 month ago

Then I should seriously take a look at it. I figured it was just another package manager.

t435621 month ago

....but you have to be able to get UV and on some platforms (e.g. a raspberry pi) it won't build because the version of rust is too old. So I wrote a script called "pv" in python which works a bit like uv - just enough to get my program to work. It made me laugh a bit, but it works anywhere, well enough for my usecase. All I had to do was embed a primitive AI generated TOML parser in it.

zahlman1 month ago

> All I had to do was embed a primitive AI generated TOML parser in it.

The standard recommendation for this is `tomli`, which became the basis of the standard library `tomllib` in 3.11.

forgotpwd161 month ago

Go has explicitly rejected adding shebang support, mandating this hack, due to being considered "abuse of resources"[0]. Rather `gorun`, which is also called a mistake by Pike, is recommended instead. And can alter this method so not to need to hardcode a path.

    /// 2>/dev/null ; gorun "$0" "$@" ; exit $?
>Good-old posix magic. If you ask an LLM, it'll say it's due to Shebangs.

Well, ChatGPT gives same explanation as article, unsurprising considering this mechanic has been repeated many times.

>none other fits as well as Go

Nim, Zig, D, all have `-run` argument and can be used in similar way. Swift, OCaml, Haskell can directly execute a file, no need to provide an argument.

[0]: https://groups.google.com/d/msg/golang-nuts/iGHWoUQFHjg/_dbL...

clktmr1 month ago

There is also yaegi, a Go interpreter, which might be a better choice for small scripts than 'go run'.

https://github.com/traefik/yaegi

dolmen1 month ago

Also, for Go oneliners: https://github.com/dolmen-go/goeval

(Disclaimer: author here)

PaulRobinson1 month ago

Mad genius stuff, this.

However... scripting requires (in my experience), a different ergonomic to shippable software. I can't quite put my finger on it, but bash feels very scriptable, go feels very shippable, python is somewhere in the middle, ruby is closer to bash, rust is up near go on the shippable end.

Good scripting is a mixture of OS-level constructs available to me in the syntax I'm in (bash obviously is just using OS commands with syntactic sugar to create conditional, loops and variables), and the kinds of problems where I don't feel I need a whole lot of tooling: LSPs, test coverage, whatever. It's languages that encourage quick, dirty, throwaway code that allows me to get that one-off job done the guy in sales needs on a Thursday so we can close the month out.

Go doesn't feel like that. If I'm building something in Go I want to bring tests along for the ride, I want to build a proper build pipeline somewhere, I want a release process.

I don't think I've thought about language ergonomics in this sense quite like this before, I'm curious what others think.

dingdingdang1 month ago

Talking about Python "somewhere in the middle" - I had a demo of a simple webview gtk app I wanted to run on vanilla Debian setup last night.. so I did the canonical-thing-of-the-month and used uv to instantiate a venv and pull the dependencies. Then attempted to run the code.. mayhem. Errors indicating that the right things were in place but that the code still couldn't run (?) and finally Python Core Dumped.. OK. This is (in some shape or form) what happens every single time I give Python a fresh go for an idea. Eventually Golang is more verbose (and I don't particularly like the mod.go system either) but once things compile.. they run. They don't attempt running or require xyz OS specific hack.

fireflash381 month ago

Gtk makes that simple python program way more complex since it'll need more than pure-python dependencies.

It's really a huge pain point in python. Pure python dependencies are amazingly easy to use, but there's a lot of packages that depend on either c extensions that need to be built or have OS dependencies. It's gotten better with wheels and manylinux builds, but you can still shoot your foot off pretty easily.

cosmic_cheese1 month ago

Python is near the top in languages that have given me trouble in other peoples' production software. Everything can be working fine and then one day the planets fall out of alignment or something and the Python portion of the software breaks and the fix is as clear as mud.

skeledrew1 month ago

I'm pretty sure the gtk dependencies weren't built by Astral, which, yes, unfortunately means that it won't always just work, as they streamline their Python builds in... unusual ways. A few months ago I had a similar issue running a Tkinter project with uv, then all was well when I used conda instead.

dingdingdang1 month ago

Yeah.. this is exactly the overall reality of the ecosystem isn't it? That being said I do hope uv succeeds in their unification effort, there's nothing worse than relying on a smattering of diff package managers and built streams to get basic stuff working. It's like a messy workshop, it works but there's a implicit cost in terms of the lack of clarity and focus for the user. It's a cost I'm not willingly paying.

skeledrew1 month ago

It may not be the grand unifier if they aren't willing to compromise. Currently I'd say conda is the "grand unifier", giving users 100% what they ask for artifacts-wise, albeit rather slowly. On the other hand, uv provides things super fast, but those things may break 5% of the time in unusual ways on unusual configs. I have no issue using both for the fullest experience.

fastasucan1 month ago

I have never experienced something like that. How did you use uv? What errors did you get?

zelphirkalt1 month ago

How were the dependencies specified? What kind of files were provided for you to instantiate the venv?

logicallee1 month ago

I haven't had the same issue with anaconda. Give it a try.

dns_snek1 month ago

I've had similar issues with anaconda, once upon a time. I've hit a critical roadblock that ruined my day with every single Python dependency/environment tool except basic venv + requirements.txt, I think. That gets in the way the least but it's also not very helpful, you're stuck with requirements.txt which tends to be error-prone to manage.

pragma_x1 month ago

I know what you mean.

For me, the dividing line is how compact the language representation is, specifically if you can get the job done in one file or not.

I have no doubt that there's a lot of Go jobs that will fit in a 500 line script, no problem. But the language is much more geared towards modules of many files that all work together to design user-defined types, multi-threading, and more. None of that's a concern for BASH, with Python shipping enough native types to do most jobs w/o need for custom ones.

If you need a whole directory of code to make your bang-line-equipped Go script work, you may as well compile that down and install it to /usr/local/bin.

Also the lack of bang-line support in native Go suggests that everyone is kinda "doing it wrong". The fact that `go run` just compiles your code to a temporary binary anyway, points in that direction.

dolmen1 month ago

I think there is still space for an alternate Go syntax for scripting with the following constraints:

* a single file exposing a "main" package and its "func main()"

* import syntax that merges requires from go.mod (import both packages and modules)

* simplified error handling (just ignore returned errors in code, and they are caught by the transpiler to be handled as fatal)

Those are my ideas to go beyond my own goeval that already allows to run Go oneliners. https://github.com/dolmen-go/goeval

exographicskip1 month ago

bitfield/script has some nice abstractions for bash builtins and coreutils

https://github.com/bitfield/script

cl3misch1 month ago

> bash obviously is just using OS commands with syntactic sugar

No, bash is technically not "more" OS than e.g. Python. It just happens that bash is (often) the default shell in the terminal emulator.

xg151 month ago

Have to disagree, "technically" yes, both are interpreted languages, but the ergonomics and mental overhead of doing certain things are wildly different:

In python, doing math or complex string or collection operations is usually a simple oneliner, but calling shell commands or other OS processes requires fiddling with the subprocess module, writing ad-hoc streaming loops, etc - don't even start with piping several commands together.

Bash is the opposite: As long as your task can be structured as a series of shell commands, it absolutely shines - but as soon as you require custom data manipulation in any form, you'll run into awkward edge cases and arbitrary restrictions - even for things that are absolutely basic in other languages.

nr3781 month ago

> In python, ..., calling shell commands or other OS processes requires fiddling with the subprocess module, writing ad-hoc streaming loops, etc - don't even start with piping several commands together.

You inspired me to throw something simpler together - https://pypi.org/project/shell-pilot/

xg151 month ago

That looks really cool!

t435621 month ago

The subprocess module is horrendous but even if it was great bash is simpler. I just think about trying to create a pipe of processes in python without the danger of blocking.

skeledrew1 month ago

I love Python and dislike Bash, but just look at the difference between listing a folder in Bash vs Python, for example.

DrewADesign1 month ago

Right. Figure out if other users are logged in using Python. Get a human-readable remaining space on each partition.

It’s not like you can’t do in Python, but it’s a whole lot more work than typing <10 characters directly into shell.

socalgal21 month ago

bash to me is the C++ of scripting. There are a bunch of arcane rules which all have to be followed, forget a single one of them and you've got a vulnerability.

skybrian1 month ago

Maybe the ergonomics of writing code is less of a problem if you have a quick way of asking an LLM to do the edits? We can optimize for readability instead.

More specifically, for the readability of code written by an LLM.

vovavili1 month ago

>I don't want to have virtual environments and learn what the difference between pip, poetry and uv is. I don't care. I just want to run the code.

So this is skill issue, the blog post. `uv run` and PEP 723 solved every single issue the author is describing.

rufius1 month ago

While this is true, it is often stunning to me how long it took to get to `uv run`.

I have worked with Python on and off for 20+ years and I _always_ dreaded working with any code base that had external packages or a virtual environment.

`uv run` changed that and I migrated every code base at my last job to it. But it was too late for my personal stuff - I already converted or wrote net new code in Go.

I am on the fence about Python long term. I’ve always preferred typed languages and with the advent of LLM-assisted coding, that’s even more important for consistency.

callc1 month ago

Well said. I’m in the same boat of being on the fence about python. I’ve been burned too many times in the past.

And even if uv was perfectly solves all of our woes, it still seems worse than languages that solve packaging and deployment with a first-party built tools.

There’s only so much lipstick and makeup you can put on a pig…

kelipso1 month ago

Yeah, the difference between static and dynamically typed languages are massive with LLM coding, and the difference seems to me exponentially larger with larger codebases.

Mawr1 month ago

It's a UX issue. The author is correct — nobody cares about all the mumbo-jambo virtualenvs or whatever other techno-babble.

The user

just

wants

to run

the damn program.

> `uv run` and PEP 723 solved every single issue the author is describing.

PEP 723 eh? "Resolution: 08-Jan-2024"

Sure, so long as you somehow magically gain the knowledge to use uv, then you will have been able to have a normal, table-stakes experience for whole 2 years now. Yay, go Python ecosystem!

Is uv the default, officially recommended way to run Python? No? Remember to wave goodbye to all the users passing the language by.

vovavili1 month ago

I don't see your point. The kind of user who will struggle to type out `uv run` will find it even more difficult to type out `//usr/local/go/bin/go run "$0" "$@"; exit`. Neither approaches are the "default, officially recommended ways to run" scripts.

I strongly encourage you to read the article to acquire the context for the conversation before commenting, which is what I assume is happening here.

lucideer1 month ago

> Is uv the default, officially recommended way to run Python? No? Remember to wave goodbye to all the users passing the language by.

uv was invented basically yesterday. But it is becoming the default faster than any tool I've ever seen.

So I'd say: Yes - it is, effectively, the default now.

wiseowise1 month ago

> Is uv the default, officially recommended way to run Python?

Yes, seems like it is de facto default, officially recommended way.

planteur1 month ago

Is it officially recommended? I don't see it listed on PyPA's Tool Recommendations [0] and Project Summaries [1] pages.

[0] https://packaging.python.org/en/latest/guides/tool-recommend... [1] https://packaging.python.org/en/latest/key_projects/

worksonmine1 month ago

> The user just wants to run the damn program.

I don't agree, the user wants to run the program in a way the user wants to, but is frustrated when it doesn't.

If all dependencies were installed on the machine the script would run no problem. I have some scripts with dependencies that are installed on the system.

The author writes:

> The built in tooling within the go ecosystem is another large selling point. We don't need a .pyproject or package.json to configure ad-hoc formatting and linters, backed by pipelines to ensure consistency.

Maybe shebangs is not the solution to that problem? It's a convenience to run scripts as executable, but the user is supposed to setup the environment. Then he continues to explain that go has a great stdlib which makes it perfect for scripting. This is the reason I usually reach for python for complex scripts, the stdlib is big enough to solve most my problems.

Now that node includes sqlite the choice isn't as easy, but I wouldn't be pissed at node and javascript if I have to setup the environment to make sure the script runs. I understand how it runs, where it gets the dependencies. If I forget to run `npm i` before running the scripts that's my error, I prefer errors that remind me of my stupidity over magic.

fastasucan1 month ago

>Sure, so long as you somehow magically gain the knowledge to use uv, then you will have

Couldn't you say that about any know how?

>Remember to wave goodbye to all the users passing the language by.

Given the size of the python user base, this doesn't seem like a big problem.

oceansky1 month ago

Any old enough language will have competing libraries that you need to learn in the long run too.

saghm1 month ago

Libraries, yes. Tooling around packages/building/managing runtimes? I'm not convinced. Perl has been using CPAN like two decades now, and I wouldn't consider that ecosystem to exactly be an example of "there's only one way to do it". I feel like you're extrapolating in the wrong direction; older languages are less likely to have first party tooling for this, so they're more likely to have multiple ways of doing it, but I don't think there's much evidence that a language that started out with first party tooling will always follow that same trend. I don't think the issue with Python is it's age as much as the tooling it has was just really not good for a long time. People will probably want to replace subpar tooling regardless of the official status if there's a better alternative, but I'd expect that I'm the presence of good enough first-party tooling, people will eventually stop bothering.

I do actually think Go is a bit of an illustrative example here because it started out with just `go get` and liberal use of vendoring, then accumulated a variety of attempted replacements (e.g. godep and dep, which confusingly were not the same thing), but eventually the first party tooling around modules became a thing and after some time it seems like pretty much everyone dropped those interim tools and standardized on the official tooling. I feel like this actually shows that the proliferation of tooling can actually be stopped even if it didn't exist early on, provided that there's a process for making it official.

karel-3d1 month ago

python packaging has been made usable by uv pretty recently.

before that it was a never ending series of approaches... that has now hopefully really been solved.

(uv is ironically not written in python, but, such is life)

rplnt1 month ago

So does that also solve the many issues

I've never worked on a super big project, so when it comes to python dependancies the issue I always had is some C/C++ packages trying to build locally and fsiling. While this is mostly a problem on windows I've encountered it on mac as well. I assume uv doesn't have a way to solve this?

andrewmcwatters1 month ago

[dead]

esjeon1 month ago

Expected a rant, got a life-pro-tip. Enough for a good happy new year.

That said, we can abuse the same trick for any languages that treats `//` as comment.

List of some practical(?) languages: C/C++, Java, JavaScript, Rust, Swift, Kotlin, ObjC, D, F#, GLSL/HLSL, Groovy

Personally, among those languages, GLSL sounds most interesting. A single-GLSL graphics demo is always inspiring. (Something like https://www.shadertoy.com/ )

Also, let’s not forget that we can do something similar using block comment(`/* … */`). An example in C:

/*/../usr/bin/env gcc "$0" "$@"; ./a.out; rm -vf a.out; exit; */

#include <stdio.h>

int main() { printf("Hello World!\n"); return 0; }

frizlab1 month ago

For Swift there’s even a project[1] that allows running scripts that have external dependencies (posting the fork because the upstream is mostly dead).

I think it’s uv’s equivalent, but for Swift.

(Also Swift specifically supports an actual shebang for Swift scripts.)

[1] https://github.com/xcode-actions/swift-sh

thechao1 month ago

For C/++ just use "#!". When TCC first came out, we used this exact technique for "C scripting". It requires a dirty SO methodology (you can't really control linking well).

For larger projects (the exe), the shebang points to a C build file, which when compiled, knows the root path; that C build script then looks for a manifest, builds, links, and fork()s. A good a/m timestamp library with direct ccache support can spin up as fast as a script even on big projects.

Again, this is all a bad idea bc it's hard to control your environment.

I guess we were doing all this in the mid 2000s? When did TCC come out?

kibwen1 month ago

You don't need to abuse comments like this for Rust, because it supports shebangs directly.

flufluflufluffy1 month ago

I don’t really understand the initial impetus. I like scripting in Python. That’s one of the things it’s good at. You can extremely quickly write up a simple script to perform some task, not worrying about types, memory, yada yada yada. I don’t like using Python as the main language for a large application.

mr_toad1 month ago

I love scripting in Python too. I just hate trying to install other people’s scripts.

enriquto1 month ago

> hate trying to install other people’s scripts.

This phrasing sounds contradictory to me. The whole idea of scripts is that there's nothing to install (besides one standard interpreter). You just run them.

lucb1e1 month ago

By that logic, you don't install an OS, you just put the bootloader and other supporting files on your storage medium of choice and run it

solid_fuel1 month ago

> The whole idea of scripts is that there's nothing to install

and yet without fail, when I try to run basically any `little-python-script.py`, it needs 14 other packages that aren't installed by default and I either need to install some debian packages or set up a virtual environment.

+1
fastasucan1 month ago
flufluflufluffy1 month ago

Anytime I have the need to write a script, I write it myself. When I do this, I like to do it in Python, rather than Go.

I very rarely, if ever, “install” or run other people’s scripts. Because typically, a script is a specialized piece of code that does something specific the user was trying to do when they wrote it.

However, I do often install applications or libraries written by other people in Python. Typically, I don’t have a problem, but sometimes I run into dependency hell. But this is something different than me just writing scripts. For scripting, Python is great.

flanked-evergl1 month ago

If they use https://packaging.python.org/en/latest/specifications/inline... then it becomes a breeze to run with uv. Not even a thing.

hu31 month ago

but then you need uv

it's not as portable

+1
networked1 month ago
zahlman1 month ago

> I just hate trying to install other people’s scripts.

This notion is still strange to me. Just... incompatible with how I understand the term "script", I guess.

Brian_K_White1 month ago

You don't understand the concept of people running software written by other people?

One of my biggest problems with python happens to be caused by the fact that a lot of freecad is written in python, and python3 writes _pycache_ directories everywhere a script executes (which means everywhere, including all over the inside of all my git repos, so I have to add _pycache_ to all the .gitignore ) and the env variable that is supposed to disable that STUPID behavior has no effect because freecad is an appimage and my env variable is not propagating to the environment set up by freecad for itself.

That is me "trying to install other people's scripts" the other people's script is just a little old thing called FreeCAD, no big.

+1
zahlman1 month ago
me-vs-cat1 month ago

I wouldn't use "script" to describe FreeCAD. Regardless, this problem is much more with FreeCAD than with Python.

> I have to add _pycache_ to all the .gitignore

I just add that, once, in my global gitignore.

graemep1 month ago

It seems to be Linux specific (does it even work on other unix like OSes?) and Linux usually has a system Python which is reasonably stable for things you need scripting for, whereas this requires go to be installed.

You could also use shell scripting or Python or another scripting language. While Python is not great at backward compatibility most scripts will have very few issues. Shell scripts are backward compatible as are many other scripting languages are very backward compatible (e.g. TCL) and they areG more likely to be preinstalled. If you are installing Go you could just install uv and use Python.

The article does say "I started this post out mostly trolling" which is part of it, but mostly the motivation would be that you have a strong preference for Go.

exographicskip1 month ago

Works on macos too (unix by way of bsd)

andoando1 month ago

I just don't get how JS is any worse as a scripting language.

bla bla bla

node bla.js

wiseowise1 month ago

It’s not worse, but Python has better batteries out of the box. Toml, csv, real multi-threading (since 3.13), rudimentary gui, much better repl (out of the box and excellent, installable ipython), argparse and a lot more.

jswny1 month ago

Bun has a lot of this built in, plus Bun shell

Kuinox1 month ago

You do have to worry about types, you always do. You have to know, what did this function return, what can you do with it.

When you know well the language, you dont need to search for this info for basic types, because you remember them.

But that's also true for typed languages.

phantasmish1 month ago

This is more than just trivially true for Python in a scripting context, too, because it doesn’t do things like type coercion that some other scripting languages do. If you want to concat an int with a string you’ll need to cast the int first, for example. It also has a bunch of list-ish and dict-ish built in types that aren’t interchangeable. You have to “worry about types” more in Python than in some of its competitors in the scripting-language space.

Brian_K_White1 month ago

Python is great for the coder, and unholy garbage for everyone else.

If you care about anyone but yourself, don't write things in python for other people to distribute, install, integrate, run, live with.

If you don't care about anyone else, enjoy python.

wiseowise1 month ago

Nonsense.

utf_8x1 month ago

If you want a more... ergonomic language, you can also use the new "run file directly" functionality in .NET 10. It supports shebangs directly and it will even install packages referenced in the script!

  #!/usr/bin/env dotnet run
  #:package Newtonsoft.Json@13.0.3
  
  using Newtonsoft.Json;
  
  Console.WriteLine(
    JsonConvert.SerializeObject(new { Hello = "world" })
  );
Even better, with the #:sdk directive, you can even serve a tiny web app directly from your "fancy shell script"...

  #!/usr/bin/env dotnet run
  #:sdk Microsoft.NET.Sdk.Web
  
  WebApplication
    .Create()
    .MapGet("/", () => "Hello from a shell script!")
    .Run();
zmj1 month ago

I wrote my first one of these today (making a tool for agents in a c# codebase). Pretty good experience, though AOT does still have some rough edges.

fiyec303751 month ago

I thought this was going to be a longer rant about how python needs to... Go away. Which, as a long time python programmer and contributor, and at one time avid proponent of the language, I would entertain the suggestion. I think all of ML being in Python is a collosal mistake that we'll pay for for years.

The main reasons being it is slow, its type system is significantly harder to use than other languages, and it's hard to distribute. The only reason to use it is inertia. Obviously inertia can be sufficient for many reasons, but I would like to see the industry consider python last, and instead consider typescript, go, or rust (depending on use case) as a best practice. Python would be considered deprecated and only used for existing codebases like pytorch. Why would you write a web app in Python? Types are terrible, it's slow. There are way better alternatives.

solatic1 month ago

100%.

With that said... there is a reason why ML went with Python. GPU programming requires C-based libraries. NodeJS does not have a good FFI story, and neither does Rust or Go. Yes, there's support, but Python's FFI support is actually better here. Zig is too immature here.

The world deserves a Python-like language with a better type system, a better distribution system, and not nearly as much dynamism footguns / rope for people to hang themselves with.

uxcolumbo1 month ago

What about Elixir?

https://pragprog.com/titles/smelixir/machine-learning-in-eli...

A Practical Guide to Machine Learning in Elixir - Chris Grainger

https://www.youtube.com/watch?v=Es08MRtSkoE

solid_fuel1 month ago

I've actually done a fair bit of ML work in Elixir, in practice I found:

1) It's generally harder to interface with existing libraries and models (example: whisperX [0] is a library that combines generic whisper speech recognition models with some additional tools like discrete-time-warping to create a transcription with more accurate time stamp alignment - something that was very helpful when generating subtitles. But because most of this logic just lives in the python library, using this in Elixir requires writing a lot more tooling around the existing bumblebee whisper implementation [1]).

but,

2) It's way easier to ship models I built and trained entirely with Elixir's ML ecosystem - EXLA, NX, Bumblebee. I trained a few models doing basic visual recognition tasks (detecting scene transitions, credits, title cards, etc), using the existing CLIP model as a visual frontend and then training a small classifier on the output of CLIP. It was pretty straightforward to do with Elixir, and I love that I can run the same exact code on my laptop and server without dealing with lots of dependencies and environment issues.

Livebook is also incredibly nice, my typical workflow has become prototyping things in Livebook with some custom visualization tools that I made and then just connecting to a livebook instance running on EC2 to do the actual training run. From there shipping and using the model is seamless, and I just publish the wrapping module as a library on our corporate github, which lets anyone else import it straight into livebook and use it.

[0] https://github.com/m-bain/whisperX

[1] https://hexdocs.pm/bumblebee/Bumblebee.Audio.Whisper.html

uxcolumbo1 month ago

Thanks for sharing your experience with Elixir and ML.

Hopefully over time Elixir's ML ecosystem will become even better.

gspr1 month ago

> NodeJS does not have a good FFI story, and neither does Rust or Go. Yes, there's support, but Python's FFI support is actually better here.

Huh. I've found Rust's FFI very pleasant to work with. I understand that Zig's is second to none, but what does Python offer in this domain that Rust (or Go) doesn't?

solatic1 month ago

Rust's problem is similar to Go's - the language makes some very strong guarantees, and FFI breaks those guarantees, so trying to work with FFI in those languages "infects" the codebase and breaks the value-add of working with the codebase to begin with.

In Rust's case, it's the necessity of wrapping FFI with unsafe. Memory deallocation e.g. cudaFree() is just part of the underlying reality; trying to handle memory management in a language with a borrow checker rather defeats the purpose of using a language with a borrow checker in the first place. Python lets library authors write __enter__ and __exit__ dunder methods to ensure that memory deallocation is handled correctly via Python context managers, which is a much more elegant abstraction. Yes, in Rust you can implement the Drop trait, but then the caller needs to remember to put the object in its own block... like I said, it's definitely possible with Rust, it's just not as nice of a story.

gspr1 month ago

I don't see how what you describe doesn't in general apply to FFI between any languages with different resource management philosophies. In particular:

> Yes, in Rust you can implement the Drop trait, but then the caller needs to remember to put the object in its own block...

Why would you need to remember to put the object in its own block? If you want to manually control deallocation, just call drop manually (or put the object in its own block if you really prefer). If you don't care, just let the Rust compiler pick a time to drop. In both cases, the most important guarantee – that drop doesn't happen while references to the object live – is still upheld.

metaltyphoon1 month ago

> Python lets library authors write __enter__ and __exit__ dunder methods to ensure that memory deallocation is handled correctly via Python context managers, which is a much more elegant abstraction

Whats stopping you from writing a WrapperPtr and the drop trait for it in Rust? This would achieve the same as the dunder methods in python

eklavya1 month ago

Real world experience for the author which they probably lack in other langs. It would be an absurd statement otherwise.

seanw4441 month ago

> The world deserves a Python-like language with a better type system, a better distribution system, and not nearly as much dynamism footguns / rope for people to hang themselves with.

Nim. The tooling is still immature though.

NetMageSCW1 month ago

C#/.Net? (Their too strong focus on worthless backwards compatibility and slow (very slow) development speed on basic language features not withstanding.)

Xeronate1 month ago

Admittedly I haven't used C# in a few years, but to my knowledge it is much more ergonomic than java and personally it's my preferred language. Only thing stopping me from using it more is it has a much smaller community than java/python etc. Wondering what you think is missing.

wiseowise1 month ago

It’s called Java.

krzyk1 month ago

Typescript?

Why replace a nice language like python with anything coming out of javascript?

chpatrick1 month ago

Its type system is miles better than Python and it has some basic stuff Python doesn't have like block scope. Functional programming is also intentionally kind of a pain in Python with the limited lambdas.

If TypeScript had the awesome python stdlib and the Numpy/ML ecosystem I would use it over Python in a heartbeat.

josephg1 month ago

Typescript also has significantly better performance. This is largely thanks to the browser wars funnelling an insane amount of engineering effort toward JavaScript engines over the last couple decades. Nodejs runs v8, which is the JavaScript engine used by chrome. And Bun uses JSC, written for safari.

For IO bound tasks, it also helps that JavaScript has a much simpler threading model. And it ships an event based IO system out of the box.

tehjoker1 month ago

you can define a named closure in python, i do it from time to time, though it does seem to surprise others sometimes. i think maybe it's not too common.

chpatrick1 month ago

I know, it's just very unergonomic.

christophilus1 month ago

Typescript is a really nice language even though it sits on a janky runtime. I’d love a subset of typescript that compiles to Go or something like that.

polynomial1 month ago

Isn't that what Project Corsa is supposed to solve?

nish__1 month ago

Project Corsa is a rewrite of the TypeScript compiler in Go. I don't think that's what's being asked for.

fiyec303751 month ago

Typescript is ubiquitous in web, and there are some amazing new frameworks that reuse typescript types on the server and client (trpc, tanstack). It's faster (than python), has ergonomic types, and a massive community + npm ecosystem. Bun advances the state of the art for runtime performance (which anthropic just bought and use for Claude code).

bdangubic1 month ago

Did you just write both:

> The only reason to use it is inertia

and

> Typescript is ubiquitous in web

:-)

+1
sinkasapa1 month ago
krzyk1 month ago

But it comes from Javascript and inherits its issues, and is used mostly because there is no other option on the web and that makes it popular, not its quality, but lack of options. So a closed ecosystem with just one language - Javascript, and any other languages that can be compiled to it. And Typescript is a bandaid to make sensible type system on top of Javascript, and it still leaves option open to use Javascript lack of rules. Concurrency in JS also looks junky.

And on readability Python also wins (if we prohibit stupidities like PEP 572)

hu31 month ago

TypeScript is great!

Has shortcomings like all languages but it brought a lot of advanced programming language concepts to the masses!

IshKebab1 month ago

Typescript is a lot nicer than Python in many ways. Especially via Deno, and especially for scripting (imports work like people want!).

There are some things that aren't as good, e.g. Python's arbitrary precision integers are definitely nicer for scripting. And I'd say Python's list comprehension syntax is often quite nice even if it is weirdly irregular.

But overall Deno is a much better choice for ad-hoc scripting than Python.

+1
IshKebab1 month ago
CamperBob21 month ago

I think all of ML being in Python is a colossal mistake that we'll pay for for years.

If ML fulfills its promise, it won't matter in the least what language the code is/was written in.

If it doesn't, it won't matter anyway.

dkarl1 month ago

I'd love to replace Python with something simple, expressive, and strongly typed that compiles to native code. I have a habit of building little CLI tools as conveniences for working with internal APIs, and you wouldn't think you could tell a performance difference between Go and Python for something like that, but you can. After a year or so of writing these tools in Go, I went back to Python because the LOC difference is stark, but every time I run one of them I wish it was written in Go.

(OCaml is probably what I'm looking for, but I'm having a hard time getting motivated to tackle it, because I dread dealing with the tooling and dependency management of a 20th century language from academia.)

rangerelf1 month ago

Have you tried Nim? Strong and static typed, versatile, compiles down to native code vía C, interops with C trivially, has macros and stuff to twist your brain if you're into that, and is trivially easy to get into.

https://nim-lang.org

dkarl1 month ago

That looks very interesting. The code samples look like very simple OO/imperative style code like Python. At first glance it's weird to me how much common functionality relies on macros, but it seems like that's an intentional part of the language design that users don't mind? I might give it a try.

eru1 month ago

Yes, Go can hardly be called statically typed, when they use the empty interface everywhere.

Yes, OCaml would be a decent language to look into. Or perhaps even OxCaml. The folks over at Jane Street have put a lot of effort into tooling recently.

jcgl1 month ago

> Yes, Go can hardly be called statically typed, when they use the empty interface everywhere.

How often are you using any/interface {}? Yes, sometimes it's the correct solution to a problem, but it's really not that common in my experience. Certainly not common in ways that actually make life hard.

Also, since generics, I've been able to cut down my use of the empty interface even further.

eru1 month ago

Yes, generics have cut down on the times you need to use the empty interface.

archargelod1 month ago

You can replace Python with Nim. It checks literally all your marks (expressive, fast, compiled, strong-typing). It's as concise as Python, and IMO, Nim syntax is even more flexible.

https://nim-lang.org

kevin_thibedeau1 month ago

And compilation is fast enough that you can run it as a script with shebang methods.

Hasnep1 month ago

I bounced off OCaml a few years ago because of the state of the tooling, despite it being almost exactly the language I was looking for. I'm really happy with Gleam now, and recommended it over OCaml for most use cases.

IshKebab1 month ago

Can you use Gleam for ad-hoc scripting? In my mind that means two requirements that most languages fail at.

1. You can import by relative file path. (Python can't.)

2. You can specify third party dependencies in a single file script and have that work properly with IDEs.

Deno is the best option I've found that has both of those and is statically typed.

I'm hoping Rust will eventually too but it's going to be at least a year or two.

dkarl1 month ago

I always assumed a runtime specialized for highly concurrent, fault-tolerant, long-running processes would have a noticeable startup penalty, which is one of the things that bothers me about Python. Is that something you notice with Gleam?

antsinmypants1 month ago

I tried out Gleam for Advent of Code this year. There was a significant difference in startup times, about 13 ms for Python and 120 ms for Gleam.

If you want something with minimal startup times then you need a language that complies to native binaries like Zig, Rust or OCaml.

ZenoArrow1 month ago

Did you consider using F#? The language is very similar to OCaml, but it has the added benefit of good tooling and a large package ecosystem (can use any .NET package).

Hasnep1 month ago

I've heard a lot of good things about F#, but I've also heard that C# has taken all the best features from F# and now development has slowed down. I don't know how true that is. It's also just some irrational anti Microsoft bias, even though I know .NET runs fine on Linux now, the idea still felt weird to me. I suspect if I'd actually tried F# I would have stuck with it.

I have looked at the Fable compiler for F# which lets you compile F# to Rust which is very cool!

ufmace1 month ago

Rust might be worth a look. It gets much closer to the line count and convenience of the dynamic languages like Python than Go, plus a somewhat better type system. Also gets a fully modern tooling and dependency management system. And native code of course.

fiyec303751 month ago

I suppose you could try typescript which can compile to a single binary using node or bun. Both bun and node do type stripping of ts types, and can compile a cli to a single file executable. This is what anthropic does for Claude code.

loic-sharma1 month ago

You might want to try Dart. It is simple, has great tooling, and compiles to native code.

Disclaimer: I work on Flutter at Google.

wiseowise1 month ago

Modern Java with Graalvm.

rahen1 month ago

"> I think all of ML being in Python is a colossal mistake that we'll pay for for years.

Market pressure. Early ML frameworks were in Lisp, then eventually Lua with Torch, but demand dictated the choice of Python because "it's simple" even if the result is cobbled together.

Lisp is arguably still the most suitable language for neural networks for a lot of reasons beyond the scope of this post, but the tooling is missing. I’m developing such a framework right now, though I have no illusions that many will adopt it. Python may not be elegant or efficient, but it's simple, and that's what people want.

Joker_vD1 month ago

Gee, I wonder why the tooling for ML in Lisp is missing even though the early ML frameworks were in Lisp. Perhaps there is something about the language that stifles truly wide collaboration?

rahen1 month ago

I doubt it considering there are massive Clojure codebases with large teams collaborating on them every day. The lack of Lisp tooling and the prevalence of Python are more a result of inertia, low barrier to entry and ecosystem lock-in.

wild_egg1 month ago

What sort of tooling is missing in Lisp? I'd love to check out your framework if you've shared it somewhere

rahen1 month ago

Lisp isn't missing anything, it's a natural fit for AI/ML. It’s the ecosystem's tooling that needs catching up.

The code hasn't reached RC yet, but I'll definitely post a Show HN once it's ready for a preview.

strunz1 month ago

I swear the only the people who care about Python types are on Hacker News comments. I've never actually worked with or met someone who cared so much about it, and the ones that care at all seem just fine with type hints.

hu31 month ago

Perhaps some people that cared moved to other languages.

And part of those who still complain are momentarily stuck with it.

Just like survivorship bias. It's productive to ponder on the issues experienced by those who never returned.

josephg1 month ago

The people we happen to work with is an incredibly biased sample set of all software engineers.

As an example, almost everyone I’ve worked with in my career likes using macOS and Linux. But there are entire software engineering sub communities who stick to windows. For them, macOS is a quaint toy.

If you’ve never met or worked with people who care about typing, I think that says more about your workplace and coworkers than anything. I’ve worked with plenty of engineers who consider dynamic typing to be abhorrent. Especially at places like FAANG.

Long before typescript, before nodejs, before even “JavaScript the good parts”, Google wrote their own JavaScript compiler called Closure. The compiler is written in Java. It could do many things - but as far as I can tell, the main purpose of the compiler was to add types to JavaScript. Why? Because googlers would rather write a compiler from scratch than use a dynamically typed language. I know it was used to make the the early versions of Gmail. It may still be in use to this day.

tayo421 month ago

How much does python really impact ml? All of the libraries are wrappers around C code that uses gpus any way, it's distributed and inference can be written in faster languages for serving anyway?

roadside_picnic1 month ago

You're thinking only about the final step where we're just doing a bunch of matrix computation. The real work Python does in the ML world is automatic differentiation.

Python has multiple excellent options for this: JAX, Pytorch, Tensorflow, autograd, etc. Each of these libraries excels for different use cases.

I also believe these are cases where Python the language is part of the reason these libraries exist (whereas, to your point, for the matrix operations pretty much any language could implement these C wrappers). Python does make it easy to perform meta-programming and is very flexible when you need to manipulate the language itself.

superice1 month ago

It’s especially frustrating that dependency hell seems to be embedded in the Python culture. The amount of “oh no this lib will only work with Python 3.10+ and a slew of other libs at random versions we won’t bother to tell you” while some other lib that it depends on will only work on “3.8.56z but not if you look at it funny and only if you say pretty please” is maddening. Semver is apparently not standard practice either.

I am probably biased against Python, so take this opinion with a grain of salt, but it feels to me like a whole ecosystem of amateur software devs (but professional ML-engineers, data scientists etc) cobbling together something that barely works.

I’m old enough at this point that I remember the whole old guard of software engineers falling over themselves to hate on JS and Node, call the ecosystem immature, and be quick to point out how that is not “real” software. But in the past 10-15 years it appears JS and Node got their shit together, while Python is still completely and utterly stuck in managing dependencies and environments like it is 2012. And if you ask professional Pythonistas about this, you always get an answer like “oh it’s actually really easy, you must not have taken the time to really look at it, because Python is easy, it’s just pseudocode look how amazing it all is”

I really wish ML hadn’t standarized on Python. As a user of ML tools ans frameworks but not a fulltime ML engineer it just constant pain.

fastasucan1 month ago

>It’s especially frustrating that dependency hell seems to be embedded in the Python culture. The amount of “oh no this lib will only work with Python 3.10+ and a slew of other libs at random versions we won’t bother to tell you” while some other lib that it depends on will only work on “3.8.56z but not if you look at it funny and only if you say pretty please” is maddening. Semver is apparently not standard practice either.

There is no problems with this in modern python if you just use the right tooling.

robomartin1 month ago

> I think all of ML being in Python is a collosal mistake that we'll pay for for years.

> The main reasons being it is slow, <snip>, and it's hard to distribute.

Don't forget that Python consumes approximately 70x more power when compared to C.

fwip1 month ago

Not really applicable to ML. The massive amount of compute running on the GPU is not executing in Python, and is basically the same regardless of host language.

robomartin1 month ago

There's a lot of non-GPU hardware surrounding every kind of data center. If this is coded in Python, it is using 70x more energy than C.

fwip1 month ago

Not in any meaningful way, no.

vovavili1 month ago

This is a needlessly dismissive and narrow-minded attitude. Have you ever tried to use FastAPI?

yunnpp1 month ago

You say he's narrow-minded, but you focus on the least relevant thing of everything he said, speed, and suggest that, somehow, something with "fast" in its name will fix it?

Speed is the least concern because things like numpy are written in C and the overhead you pay for is in the glue code and ffi. The lack of a standard distribution system is a big one. Dynamic typing works well for small programs and teams but does not scale when either dimension is increased.

But pure Python is inherently slow because of language design. It also cannot be compiled efficiently unless you introduce constraints into the language, at which point you're tackling a subset thereof. No library can fix this.

vovavili1 month ago

Very little of what you're claiming is relevant for FastAPI specifically, which in terms of speed isn't too far from an equivalent app written in Go for writing a web app. You need to research the specifics of a problem at hand instead of making broad but situationally incorrect assumptions. The subject here is web apps, and Python is very much a capable language in this niche as of the end of 2025, both in terms of speed, code elegance and support for static typing (FastAPI is fully based on Pydantic) - https://www.techempower.com/benchmarks/#section=test&runid=7...

spockz1 month ago

> But pure Python is inherently slow because of language design. It also cannot be compiled efficiently unless you introduce constraints into the language, at which point you're tackling a subset thereof. No library can fix this.

A similar point was raised in the other python thread on cpython the other day, and I’m not sure I agree. For sure, it is far from trivial. However, GraalVM has shown us how it can be done for Java with generics. Highover, take the app, compile and run it. The compilation takes care of any literal use of Generics, running the app takes care of initialising classes and memory, instrumentation during runtime can be added to add runtime invocations of generics otherwise missed. Obviously, this takes a lot of details getting it right for it to work. But it can be done.

fiyec303751 month ago

Yes. Have you ever tried trpc?

vovavili1 month ago

Implying that existence of your tool of preference in another programming language makes other equally impressive tools something akin to "[colossal] mistake that we'll pay for for years" "simply motivated by inertia" is way below the level of discussion I would expect from Hacker News.

+1
carderne1 month ago
g947o1 month ago

While you are at it, might as well do this for C++ or assembly. You hate scripting so much and would rather go to great lengths to use a complied language and throw away all the benefits of a scripting language and scripting itself, just because you don't like the language, not because of technical merit. Congratulations, you just wasted yourself many hours of precious time.

> The price of convenience is difficulties to scale

Of course, they never scale. The moment you start thinking about scaling, you should stop writing code as throwaway scripts but build them properly. That's not an argument to completely get rid of Python or bash. The cost of converting Python code to Go is near zero these days if there is a need to do so. Enough has been said about premature optimization.

> Anyone who's ever tried to get python working on different systems knows what a steep annoying curve it is.

If you need 10 libraries of certain versions to run a few lines of Python code, nobody calls that a script anymore. It becomes a proper project that requires proper package management, just like Go.

BobbyJo1 month ago

There is a much larger gap in language ergonomics between python and C++ than between python and golang. Compile time and package management being some of the major downsides to C++.

"You'd rather drive a compact car than an SUV? Might as well drive a motorcycle then!"

array_key_first1 month ago

The main problem with python for system scripts is that's even in that domain it's not a very good choice.

Perl is right there, requires no installation, and is on practically every unix-like under the sun. Sure it's not a good language, or performant, or easy to extend, but neither is python, so who cares. And, if anything, it's a bit more expressive and compact than python, maybe to a fault.

magicalhippo1 month ago

You can do the same[1] with .Net Core for those of us who like that.

[1]: https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals...

zahlman1 month ago

That's explicit support rather than using the same // hack. The language is specifically ignoring a shebang even though it doesn't match the usual comment syntax.

magicalhippo1 month ago

True, so less fun, but more practical in that it's supported.

rr8081 month ago

dotnet was always really good for this. There were a bunch of third party tools that have done this since the 90s like toolsack.

I think Java can run uncompiled text scripts now too

lucideer1 month ago

> I don't want to have virtual environments and learn what the difference between pip, poetry and uv is. I don't care. I just want to run the code.

The author is right that every user approaching Python shouldn't need to figure this out. However, I do strongly believe every blogger setting out to write an opinion piece on this topic should at least attempt to figure this out before writing their article.

Obvious ignorance does not a compelling point make. Especially when uv solves literally all of the problems you're describing.

abbadadda1 month ago

> Especially when uv solves literally all of the problems you're describing.

How does uv solve the “write once, run anywhere” problem like go?

(I’m not being sarcastic, I don’t know that much about uv and perhaps am lacking the mental model for understanding this)

lucideer1 month ago

> How does uv solve the “write once, run anywhere” problem like go?

Here's an examply Python script from the uv website[0]:

  #!/usr/bin/env -S uv run --script
  #
  # /// script
  # requires-python = ">=3.12"
  # dependencies = ["httpx"]
  # ///

  import httpx

  print(httpx.get("https://example.com"))

This will auto-install Python 3.12 (or greater) in a per-script virtual environment, along with the httpx package from pypi & immediately execute the script, on any system that has uv.

[0] https://docs.astral.sh/uv/guides/scripts/#using-a-shebang-to...

Quothling1 month ago

UV is a drop-in for pip, rather than a replacement. It functions differently than pip behind the scenes, which is why it's faster, but when you use it, it's basically still pip. Except it makes everything easy. You create your venv with uv venv, you update packages with uv sync, you run things without activating your venv with uv run... It uses the pyproject.toml, so it's very easy to share things like linters and build tools. Then when it's time to deploy, you can compile your pyproject.toml to a requirements.txt file and use it in any Python container image. Which is very handy when you work with something like Azure Container Functions which don't come with UV (and you probably don't want to use an UV image or install UV in your build process).

I've been using it for so long now that I recently couldn't remember how to use Python without it, when one of our BI guys needed some help. Which was ridiculously embarrassing.

I don't think it really compares to Go though. It's not as straight forward if you work on different python distributions. It's also not as easy to work with something like micro Python compared to targeting a specific embedded platform with Go.

fsmv1 month ago

I made one of these too! I decided not to use // because I use gofmt auto formatting in my editor and it puts a space between the // and the usr. This one isn't changed by gofmt:

    /*?sr/bin/env go run "$0" "$@"; exit $? #*/
beckit28 days ago

This is a sweet idea, thank you!

My suggestion for minimal/readable:

  /*usr/bin/env go run "$0" "$@"; exit;*/

  - go fmt leaves it alone
  - it preserves the exit code in case env or go breaks
  - it "figures out" where go is, no need to hard code a path that breaks on yet another machine (where /usr/bin/env is "standard")

  ( 
  The first example:
  - I'm not a big fan of the subtle extra work that the question mark in the original path imposes on the system.  env is (almost) always going to be as /usr/bin/env and rarely would something else that matches exist.  ls -d /?sr to see what outputs on your system.  That being said, the extra work isn't very meaningful in this case...

  - The trailing $? seems unnecessary as the final exit will convey the return code from env go regardless of if the $? is present or not
  )
k77628 days ago

I wonder if bash has a way to override the default function calling mechanism. Since functions in bashrc would probably take priority, maybe something like this could be possible: (psuedo code)

```

execve(path, args) {

  if path.endsWith('.go') {

    go run path args

  } else {

    super($path, *args)

  }
}

```

Then when you run `./script.go`, the function gets called and passes it so `go run`, while everything else goes through the existing execve function. That would be interesting, because then you could do this with absolutely anything.

`./script.rb`

`./script.py`

`./script.c`

etc

tandr1 month ago

It works, but the best in me I cannot explain fully first 3 symbols. /*?sr/bin/env finds /usr by expanding *? to a first matching directory. But why not just /*usr/ instead?

fsmv1 month ago

I think I was just trying to minimize accidentally matching the wrong thing. Both do work though and it is kinda nice to be more readable.

If I remember right I think ? Is exactly one character only, or maybe it does non greedy .

adonovan1 month ago

> The one big problem: gopls. We need the first line of the script to be without spaces...

Specifically the problem here is automated reformatting. Gopls typically does this on save as you are editing, but it is good practice for your CI system to enforce the invariant that all merged *.go files are canonically formatted. This ensures that the user who makes a change formats it (and is blamed for that line), instead of the hapless next person to touch some other spot in that file. It also reduces merge conflicts.

But there's a second big (bigger) problem with this approach: you can't use a go.mod file in a one-off script, and that means you can't specify versions of your dependencies, which undermines the appeal to compatibility that motivated your post:

> The primary benefit of go-scripting is [...] and compatibility guarantees. While most languages aims to be backwards compatible, go has this a core feature. The "go-scripts" you write will not stop working as long as you use go version 1.*, which is perfect for a corporate environment.

> In addition to this, the compatibility guarantees makes it much easier to share "scripts". As long as the receiving end has the latest version of go, the script will run on any OS for tens of years in the future.

kardianos1 month ago

True, but major versions are locked in through the import path and should be compatible.

arccy1 month ago

> which undermines the appeal to compatibility that motivated your post

not really? this is about the language / core runtime rather than any dependencies.

w4rh4wk51 month ago

Back in the days, I've seen that with C files, which are compiled on the fly to a temporary file an run.

Something like //usr/bin/gcc -o main "$0"; ./main "$@"; exit

ernst_klim1 month ago

Tcc even supports that with `#!/usr/local/bin/tcc -run`, although I don't understand people who use c or go for "scripting", when python, ruby, TCL or perl have much superior ergonomics.

w4rh4wk51 month ago

This was a relatively old project that used a C program as build system / meta generator. All you needed was a working C compiler (and your shell to execute the first line). From there, it built and ran a program that generated various tables and some source code, followed by compiling the actual program. The final program used a runtime reflection system, which was set up by the generated tables and code from the first stage.

The main reason was to do all this without any dependencies beyond a C compiler and some POSIX standard library.

age123456gpg1 month ago

Official stance about supporting interpreter mode for the reference https://github.com/golang/go/issues/24118

trvv1 month ago

You can lose the ugly "exit" at the end and space it right for formatting too

    // 2>/dev/null; exec go run "$0" "$@"
kazinator1 month ago

That's a neat trick; I don't think I've seen that before. It can be made to work for just about any language that has // comments.

It does rely on // which is implementation-defined according to POSIX. In some system //usr could refer to some kind of network path.

Last sentence here:

3.254 Pathname

A string that is used to identify a file. In the context of POSIX.1-2024, a pathname may be limited to {PATH_MAX} bytes, including the terminating null byte. It has optional beginning <slash> characters, followed by zero or more filenames separated by <slash> characters. A pathname can optionally contain one or more trailing <slash> characters. Multiple successive <slash> characters are considered to be the same as one <slash>, except it is implementation-defined whether the case of exactly two leading <slash> characters is treated specially.

[IEEE Std 1003.1, 2024 Edition]

It really is better for a language to either have # comments, or else support #! as a special case in a file that is presented for execution. You're also not launching an extra shell instance. (Too bad this // trick cannot use the "exec" shell command to replace the shell with the go program.)

evanmoran1 month ago

In a similar way I changed all of my build and deployment scripts to Go not long ago. The actual benefit was utility functions used by the service could be shared in deployment. So I could easily share code to determine if services/dbs were online or to access cloud secrets in a uniform way. It also improved all the error checks to be much clearer (did the curl fail because it’s offline or malformed).

Additionally, it is even more powerful when used with go modules. Make every script call a single function in the shared “scripts” module and they will all be callable from anywhere symmetrically. This will ensure all scripts build even if they aren’t run all the time. It also means any script can call scripts.DeployService(…) and they don’t care what dir they are in, or who calls it. The arguments make it clear what paths/configuration is needed for each script.

itopaloglu831 month ago

> Sidetrack: I actually looked up what the point of arg0 even is since I failed to find any usecases some months back and found this answer.

I think arg0 was always useful especially when developing multifunctional apps like busybox that changes its behavior depending on the name it was executed as.

rtpg1 month ago

You don't even need to end the file in `.go` or the like when using shebangs, and any self-respecting editor will be good at parsing out shebangs to identify file types (... well, Emacs seems to do it well enough for me)

no need to name your program foo.go when you could just name it foo

kbolino1 month ago

The `go run` tool will not execute (or even recognize) a file that does not end in .go, so this is not good advice.

rtpg1 month ago

Unfortunate! Yet another little bit of Go design decision that makes things worse for, in my opinion, no reason.

api1 month ago

Tangent but... I kinda like the Python language. What I don't like about Python is the way environments are managed.

This is something I generally believe, but I think it's particularly important for things like languages and runtimes: the idea of installing things "on" the OS or the system needs to die.

Per-workspace or per-package environment the way Go, Rust, etc. does it is correct. Installing packages globally is wrong.

There should not be such a thing as "globally." Ideally the global OS should be immutable or nearly so, with the only exception being maybe hardware driver stuff.

(Yes I know there's stuff like conda, but that's yet another thing to fix a fundamentally broken paradigm.)

zahlman1 month ago

> This is something I generally believe, but I think it's particularly important for things like languages and runtimes: the idea of installing things "on" the OS or the system needs to die.

Python has been trying to kill it for years; or at least, the Linux distros have been seeking Python's help in killing it on Linux for years. https://peps.python.org/pep-0668/ is the latest piece of this.

api1 month ago

I feel like this principle could be codified as "the system is not a workspace."

The use of the system as a workspace goes back to when computers were either very small and always personal only to one user, or when they were very big and administrated by dedicated system administrators who were the only ones with permission to install things. Both these conditions are obsolete.

NetMageSCW1 month ago

But the system is not a workspace acts like resources are free. Everything that’s wrong with a modern computer being slower than one from 30 years ago at running user applications has its roots in this kind of thing. It’s more obvious on mobile devices but desktops still suffer. Android needs more RAM and had worse power utilization until a lot was done to move toward native compiled code and background process control. Meanwhile Electron apps think it’s okay to run multiple copies of Javascript environments like working RAM is free and performance isn’t hurt.

kstrauser1 month ago

Perhaps, but that's not really relevant here. Python's virtualenvs wouldn't increase RAM usage any more than using the system-wide environment.

petercooper1 month ago

Cute trick! I pointlessly wondered if I could make it work with Ruby and you kinda can, if you can tolerate a single error message before the script runs (sadly # comments don't work as shells consider them comments too):

    =begin
    ruby $0; exit
    =end

    puts "Hello from Ruby"
Not immediately useful, but no doubt this trick will pop up at some random moment in the future and actually be useful. Very basic C99 too, though I'm not sure I'd want to script with it(!):

    //usr/bin/cc $0 && ./a.out && exit
codelikeawolf1 month ago

> Did you (rightfully) want to tear your eyes out when some LLM suggested that you script with .mjs?

I respectfully disagree with this sentiment. JS is a fantastic Python replacement for scripts. Node.js has added all kinds of utility functions that help you write scripts without needing external dependencies. Bun, Deno, and Node.js can execute TS files (if you want to bring types into the mix). All 3 runtimes are sufficiently performant. If you do end up needing external dependencies, they're only a package.json away. I write all my scripts in JS files these days.

jswny1 month ago

The problem is that only Deno can type check single file scripts. Otherwise with Node and Bun you need a project to use tsc. Python can type check single file scripts (even with PEP 723 deps) with ty. Otherwise, I love TS for scripting, especially with Bun shell

codelikeawolf1 month ago

Fair point. If I'm writing scripts, I normally opt for a JS file with JSDoc annotations if I want autocomplete for types (you don't need a tsconfig/tsc installed in WebStorm). Most projects I've encountered with TS scripts were already TS projects to begin with.

marifjeren1 month ago

> don't want to have virtual environments and learn what the difference between pip, poetry and uv is

Oh come on, it's easy:

Does the project have a setup.py? if so, first run several other commands before you can run it. python -m venv .venv && source .venv/bin/activate && pip install -e .

else does it have a requirements.txt? if so python -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt

else does it have a pyproject.toml? if so poetry install and then prefix all commands with poetry run ...

else does it have a pipfile? pipenv install and then prefix all commands with pipenv run ...

else does it have an environment.yml? if so conda env create -f environment.yml and then look inside the file and conda activate <environment_name>

else does it have a uv.ock? then uv sync (or uv pip install -e .) and then prefix commands with uv run.

zahlman1 month ago

> Oh come on, it's easy: (satire)

If you've checked out a repo or unpacked a tarball without documentation, sure.

If you got it from PyPI or the documentation indicates you can do so, then you just use your tooling of choice.

Also, the pip+venv approach works fine with pyproject.toml, which was designed for interoperability. Poetry is oriented towards your own development, not working with someone else's project.

Speaking of which, a project that has a pipfile, environment.yml, uv.lock etc. and doesn't have pyproject.toml is not being seriously distributed. If this is something internal to your team, then you should already know what to do anyway.

NetMageSCW1 month ago

Any time you have to resort to the no true scotsman fallacy you are telling me everything I need to know to run in the other direction.

zahlman1 month ago

It is not "no true scotsman" to point out that tons of projects are put on GitHub etc. without caring about whether others will actually be able to download and "install" and use the code locally, and that it's unreasonable to expect ecosystems to handle those cases by magic. To the extent that a Python ecosystem exists and people understand development within that ecosystem, the expectations for packaging are clear and documented and standard.

Acting as if these projects using whatever custom tool (and its associated config, by which the tool can be inferred), where that tool often isn't even advertised as an end-user package installer, are legitimate distributions is dishonest; and acting as if it reflects poorly on Python that this is possible, far more so. Nothing prevents anyone from creating a competitor to npm or Cargo etc.

9dev1 month ago

And you consider that easy?

marifjeren1 month ago

No, I'm being cheeky. It's not fun. It's a "15 standards" situation https://xkcd.com/927/

ikrenji1 month ago

I never had any trouble setting up or working with venvs. I don't even feel the need to learn what uv is because it's such a non problem for me.

chrismorgan1 month ago

One suggestion: change `exit` to `exit $?` so an exit code is passed back to the shell.

mikeschinkel29 days ago

Nice technique, but wondering if the trailing "; exit" results in consuming non-zero exit codes passed to os.Exit() in the Go code?

emersion1 month ago

The following would probably be more portable:

    ///usr/bin/env go run "$0" "$@"; exit
Note, the exit code isn't passed through due to: https://github.com/golang/go/issues/13440
zahlman1 month ago

Does the third leading slash do something?

emersion1 month ago

No, it just felt a tad cleaner to have the comment slashes separate from the path leading slash.

incognito1241 month ago

To quote the blog in question:

> How true this is, is a topic I dare not enter.

loosescrews1 month ago

The blog says that in regard to finding bash with env. My reading is that it does not make the same claim regarding finding go with env. bash is commonly found at /bin/bash (or a symlink there exists) as it is widely used in scripts and being available at that path is a well known requirement for compatibility. Go does not so much have a conical path and I have personally installed it at a variety of paths over the years (with the majority working with env). While I agree with the author of the blog that using env to find bash may or may not improve compatibility, I also agree with the parent comment that using env to find go probably does improve compatibility.

morisil1 month ago

Suboptimal choice. According to AutoCodeBench, for equivalent problem complexity, LLMs generate correct Kotlin code ~70% of the time versus ~40% for Python, and Go scores lower than Python. Kotlin can be executed as a script while providing super fast compilation phase next to evaluation phase, which is further reducing a chance of mistakes. I don't use tools anymore. I just let my LLMs output Kotlin script directly together with DSLs tailored to the problem space, reducing cognitive load for the machine. It works like a charm as a Claude Code replacement, not only coding autonomously in any language, but directly scripting DB data science, Playwright, etc., while reducing context window bloat.

jas391 month ago

May I...

augroup fix autocmd! autocmd BufWritePost *.go \ if getline(1) =~# '^// usr/bin/' \ | call setline(1, substitute(getline(1), '^// ', '//', '')) \ | silent! write \ | endif augroup END

throw-12-161 month ago

I've been meaning to port some dotfiles utils over to go, I think I'll give this a shot.

chrisweekly1 month ago

Related tangent: I recently learned about Mise^1 -- a tool for managing multiple language runtime versions. It might ease some of the python environment setup/mgmt pains everyone complains about. It apparently integrates with uv, and can do automatic virtualenv activation....

1. https://mise.jdx.dev/lang/python.html

via https://gelinjo.hashnode.dev/you-dont-need-nvm-sdkman-pyenv-...

yawaramin1 month ago

I have a better idea: `ocaml script.ml` ;-)

Get started here: https://dev.to/yawaramin/practical-ocaml-314j

dherman1 month ago

Ha, I just tried the same trick with Rust:

  //$HOME/.cargo/bin/rustc "$0" && ${0%.rs} "$@" ; exit
  
  use std::env;
  
  fn main() {
      println!("hello, world!");
      for arg in env::args() {
          println!("arg: {arg}");
      }
  }
Total hack, and it litters ./ with the generated executable. But cute.
eolpin1 month ago

Fortunately this hack isn't necessary for rust, shebangs are syntactically valid and single-file scripts can be executed with cargo nightly: https://doc.rust-lang.org/nightly/cargo/reference/unstable.h...

jswny1 month ago

I wish this was faster, in my tests it’s about 200ms startup consistently on my M4 MacBook. Otherwise very cool

eolpin1 month ago

Interesting! That seems unexpected, for a minimal hello-world program I'm averaging 14ms after the first execution:

```

$ echo 'fn main() { println!("hello, world!") }' > file.rs; hyperfine --warmup 1 'cargo +nightly -Zscript file.rs'

Benchmark 1: cargo +nightly -Zscript file.rs

  Time (mean ± σ):      14.0 ms ±   1.0 ms    [User: 7.7 ms, System: 9.7 ms]

  Range (min … max):    12.2 ms …  17.5 ms    203 runs
```
jswny1 month ago

Ah I think it’s because I was testing with an external library in the top level TOML metadata

dherman1 month ago

Cool!

timcavel1 month ago

[dead]

paulddraper1 month ago

Go is poor for cheap scripts for one reason: error handling.

It’s great for “robust” code, not for quick things that you’re okay with exploding in the default way.

rajaravivarma_r1 month ago

If Python's standard library and its backward incompatible changes are the only problem, then Ruby will be a great replacement. The language is terse and the standard library is beautiful and consistent. The language doesn't have any large backward incompatible change from version 1.9 which was released 15 years ago.

erlkonig1 month ago

Fine advice overall.

Except:

Don't put suffixes on command names. Don't. It's a DOS thing that has no meaning in Unix. It confuses users. It breaks hiding implementation details. It encourages users to do the wrong thing. It makes changing what language a script's in have a ripple effect of breakage across everything else that uses it.

Don't do it.

Zababa1 month ago

>This second method is, by the way, argued to increase compatibility as we utilize env to locate bash, which may not be located at /bin/bash. How true this is, is a topic I dare not enter.

At least it seems important on NixOS, I had to rewrite a few shebangs on some scripts that used /bin/bash and didn't work on NixOS.

benchloftbrunch1 month ago

And on macOS if you need bash > 3.2

rewilder121 month ago

I've tried Go scripting but would still still prefer python (uv is a game changer tbh). My go-to for automation will always be powershell (on linux) though. It's too bad PowerShell has the MSFT ick keeping people away from adopting it for automation. I can convince you to give it a try if you let me

networked1 month ago

You can use https://github.com/erning/gorun as a Go script runner. It lets you embed `go.mod` and `go.sum` and have dependencies in Go scripts. This is more verbose than Python's inline script metadata and requires manual management of checksums. gorun caches built binaries, so scripts start quickly after the first time.

Example:

  #! /usr/bin/env gorun
  //
  // go.mod >>>
  // module foo
  // go 1.22
  // require github.com/fatih/color v1.16.0
  // require github.com/mattn/go-colorable v0.1.13
  // require github.com/mattn/go-isatty v0.0.20
  // require golang.org/x/sys v0.14.0
  // <<< go.mod
  //
  // go.sum >>>
  // github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
  // github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
  // github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
  // github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
  // github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
  // github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
  // github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
  // golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
  // golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
  // golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
  // golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
  // <<< go.sum

  package main

  import "github.com/fatih/color"

  func main() {
      color.Green("Hello, world!")
  }
The shebang line can be replaced for compatibility with standard Go tooling:

  /// 2>/dev/null ; gorun "$0" "$@" ; exit $?
  //
  // go.mod >>>
  // ...
karel-3d1 month ago

I was looking for something like this a few times! great

alkh1 month ago

Have to post this monstrocity that let's you either run a python script with uv or with python directly if uv is not installed(for some of my collegues)

#!/usr/bin/env bash

""":"

if command -v uv > /dev/null

then exec uv run --script "$0" "$@"

else

exec python3 "$0" "$@"

fi

":"""

commandersaki1 month ago

One thing I hate about Python executables, at least the ones I've seen installed in Debian/Ubuntu is that the ones in /usr/bin are wrappers to execute somewhere in your site-packages.

I just want to see the full script where I execute it.

kstrauser1 month ago

Can you show an example of that?

commandersaki1 month ago

Good thing you asked before I destroyed this ubuntu instance I was messing with:

    root@t:~# cat $(which scapy)
    ++ which scapy
    + cat /usr/bin/scapy
    #!/usr/bin/python3
    # EASY-INSTALL-ENTRY-SCRIPT: 'scapy==2.5.0','console_scripts','scapy'
    import re
    import sys
    
    # for compatibility with easy_install; see #2198
    __requires__ = 'scapy==2.5.0'
    
    try:
        from importlib.metadata import distribution
    except ImportError:
        try:
            from importlib_metadata import distribution
        except ImportError:
            from pkg_resources import load_entry_point
    
    
    def importlib_load_entry_point(spec, group, name):
        dist_name, _, _ = spec.partition('==')
        matches = (
            entry_point
            for entry_point in distribution(dist_name).entry_points
            if entry_point.group == group and entry_point.name == name
        )
        return next(matches).load()
    
    
    globals().setdefault('load_entry_point', importlib_load_entry_point)
    
    
    if __name__ == '__main__':
        sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
        sys.exit(load_entry_point('scapy==2.5.0', 'console_scripts', 'scapy')())
kstrauser1 month ago

Gotcha.

I do understand your point, but that's pretty common for Python. Say you're using poetry (https://python-poetry.org/docs/pyproject/#scripts) or Uv (https://docs.astral.sh/uv/concepts/projects/config/#command-...) or setuptools (https://setuptools.pypa.io/en/latest/userguide/entry_point.h...) to define "scripts" for your package. Those tools will autogenerate a file like the one you posted here that basically import the package and then call a specific function inside it.

Almost all interesting Python scripts import another module, so it's really unusual to see all of a program's code in one file anyway. These autogenerated scripts just take it a step further.

chamomeal1 month ago

For another excellent scripting solution that has - fast startup (no compilation) - uses a real language - easy to upgrade beyond a script - has tons of excellent dependencies baked-in

Look no further than babashka! It’s a clojure interpreter that has first class support scripting stuff. Great built in libs for shelling out to other programs, file management, anything http related (client and server), parsing, html building, etc.

Babashka is my go-to tool for starting all new projects now. It has mostly everything you need. And if it’s missing anything, it has some of the most interesting and flexible dependency management of any runtime I’ve ever seen. Via the “pod protocol” any process (written in go/rust/java whatever) can be exposed as a babashka dependency and bundled straight in. And no separate “install dependencies” command is required, it installs and caches things as needed.

And of course you keep all of the magic of REPL based development. It’s got built in nrepl support, so just by adding on ‘—nrepl-server 7888’ to your command, you can connect to it from your editor and edit the process live. I’m building my personal site this way and it’s just SO nice.

Sorry for the rant but when superior scripting solutions come up, I have to spread the love for bb. It’s too good to not talk about!!

esaym1 month ago

> My proposition for compatibility is to not use dependencies, and instead rely on the standard library

Funny that was the whole attack angle python used against perl back in 2005,etc.

rcarmo1 month ago

Disregarding the clickbait, we’ve been able to do file suffix aliasing with zsh for a decade or so now—no need for that convoluted hack.

sharno1 month ago

You can also use jbang to run java scripts with dependencies

https://www.jbang.dev/

liveoneggs1 month ago

what's even cooler is when the language comes with first class support for this: https://www.erlang.org/docs/18/man/escript

Or the venerable https://babashka.org/

bushbaba1 month ago

This is great. Means my scripts in a golang repo can also be written in Golang vs bash/python. It can even import libs from my project.

Awesome!

arccy1 month ago

i tried this and security pinged me about behavior based security rules firing because it looks like an infostealer...

javascripthater1 month ago

Yo why do I need javascript to read your blog post when I can load it myself here as a normal page https://lorentz.app/modules/blog/content/go-shebang/_.html

jasonm231 month ago

use uv ... convince everyone to use uv, convince all docs in projects you use, to use uv.

Reject EVERYTHING ELSE for running / building py pip, poetry, conda ... etc.

fsiefken1 month ago

Arguably Crystal, Odin, Nim, Zig, V, Vale or Factor are even better!

zenethian1 month ago

What a weird bunch of hacks just to stick it to Python.

df0b9f169d541 month ago

See also https://blog.cloudflare.com/using-go-as-a-scripting-language... and https://gist.github.com/posener/73ffd326d88483df6b1cb66e8ed1... . They explained the direct use of "go run" was not good in some scenario. Is that still applied today?

> go run does not properly return the script error code back to the operating system and this is important for scripts, because error codes are one of the most common ways multiple scripts interact with each other and the operating system environment.

spacecow1 month ago

> Sidetrack: I actually looked up what the point of arg0 even is since I failed to find any usecases some months back and found this answer[0]. Confused, and unsatisfied by the replies, I gave up trying to understand "why arg0?" as some sort of legacy functionality.

I struggle to think of how the answers provided here could be clearer or more satisfactory. Why write an article if you're going to half-ass your research? Why even mention this nothingburger sidetrack at all...? (Bait?)

[0] https://stackoverflow.com/questions/24678056/linux-exec-func...

knodi1 month ago

Yes, you can but should you?

dare9441 month ago

> I started this post out mostly trolling

So your goal was to waste your reader's time. Thanks.

pvtmert1 month ago

came here to say the main trick has also been possible for C and Java. The C version has already been pointed out in sibling comments, while Java one requires more "tricks" in terms of bash substitution.

I remember I built a such java "interpreter" during my first year of university (10+ years ago! time flies fast...) because the initial intro/101 courses always had one-off programs and the main language was java.

although I no longer have the original source(s) it was something like

///usr/bin/env javac $0 && java ${0%%.java}; exit; # /

since it's quite simple, I even had a funny named wrapper called "java-script" (pun intended) in my $PATH so that I could just write

//usr/bin/env java-script # /

at the top. As you can see I already confused fellow students with the great naming scheme I used :)

qart1 month ago

There is a talk called "Java for Small Coding Tasks" by the author of Core Java: https://www.youtube.com/watch?v=04wFgshWMdA

Apparently, this is the way to do it:

    ///usr/bin/env java --source 25 "$0" "$@" ; exit $?
I don't do Java. As one comment on the video said, 'This presentation is the epitome of the old saying: "When your only tool is a hammer, every problem looks like a nail"'
jxbdbrhcb1 month ago

argv0 is very necessary for use cases like busybox

rubymamis1 month ago

What about Mojo?

loglog1 month ago

It was a terrible idea to market it as Python-compatible, and they are now slowly walking away from it.

lgas1 month ago

What about it?

vjay151 month ago

really good post thanks lorentz :D

semiinfinitely1 month ago

skill issue

mlmonkey1 month ago

I remember when I first experienced golang, I tried compiling it.

The compilation command returned immediately, and I thought it had failed. So I tried again and same result. WTF? I thought to myself. Till I did an `ls` and saw an `a.out` sitting in the directory. I was blown away by how fast the golang compiler was.

solumos1 month ago

> I started this post out mostly trolling, but the more I've thought about it's not a terrible idea.

I feel like this is the unofficial Go motto, and it almost always ends up being a terrible idea.

hu31 month ago

for more terrible ideas in 2026 then!

avidphantasm1 month ago

Now try to call some C++ code from your Go script…

dana3211 month ago

Rust can do that

llmslave21 month ago

I love it. I'm using Go to handle building full stack javascript apps, which actually works great since esbuild can be used directly inside a Go program. The issue is that it's a dependency, so I settled for having a go mod file and running it directly with Go. If somehow these dependencies could be resolved without an explicit module configured (say, it was inline in the go file itself) it would be perfect. Alas, it will probably never happen.

That being said...use Go for scripting. It's fantastic. If you don't need any third party libraries this approach seems really clean.

ahartmetz1 month ago

>full stack

Device drivers, task switching, filesystem, memory management and all?

dangoodmanUT1 month ago

dwight shrute detected

ahartmetz1 month ago

At least spell my name correctly ffs, it's right there in the org chart

mstipetic1 month ago

Don't be that guy.

ahartmetz1 month ago

I am going to be that guy.

I make computers do things, but I never act like my stuff is the only stuff that makes things happen. There is a huge software stack of which my work is just the final pieces.

+2
mstipetic1 month ago
bheadmaster1 month ago

I agree with you in sentiment - the term "full-stack" is odd and a little too grandiose for its meaning.

But it is already established in the industry, and fighting it is unlikely to yield any positive outcomes.

llmslave21 month ago

Yes. Yes, I'm doing all of that with Javascript :P

timcavel1 month ago

[dead]

assanineass1 month ago

[dead]

wiseowise1 month ago

[flagged]

flanked-evergl1 month ago

Using `uv` with python is significantly safer and better. At least you get null safety. Sure, you can't run at the speed of light, but at least you can have some decent non-halfarsed-retrofitted type checking in your script.

kbolino1 month ago

In what way does Python have more null safety than Go? Using None will cause exceptions in basically all the same places using nil will cause panics in Go, and Python similarly lacks the usual null-safe operators like traversal (?.), coalescing (??), etc.

You can abuse the falsity of None to do things like `var or ""`, but this ground gets quite shaky when real bools get involved.

tgv1 month ago

I think you're mistaking Go for some other language.

shevy-java1 month ago

    Try the following in sh:

    ////////usr/local/go/bin/go 
Well, how about this: I use ruby or python. And not shell.

Somehow I have been doing so since +25 years. Never regretted it. Never really needed shell either. (Ok, that's not entirely true; I refer to shell scripts. I do use bash as my primary shell, largely due to simplicity; I don't use shell scripts though, save for keeping a few legacy ones should I be at a computer that has no support for ruby, python or perl. But this is super-rare nowadays.)

xg151 month ago

So the entire reason why this is not a "real" shebang and instead takes the roundtrip through the shell is because the Go runtime would trip over the # character?

I think this points to some shortcomings of the shebang mechanism itself: That it expects the shebang line to be present and adhering a specific structure - but then passes the entire file with the line to the interpreter where the interpreter has to process (and hopefully ignore) the line again.

I know that situations where one piece of text is parsed by multiple different systems are intellectually interesting and give lots of opportunities for cleverness - but I think the straightforward solution would be to avoid such situations.

So maybe the linux devs should consider adding a new form for the shebang where the first line is just stripped before passing the file contents to the interpreter.

wyufro1 month ago

It doesn't pass the file contents at all, it passes the file path.

kbolino1 month ago

Yep, this is a common misunderstanding, and the blog post itself repeats it.

The only way to "pass the file contents" would be through the standard input stream, but the script might want to use stdin like normal, so this isn't an option.

eichin1 month ago

Linux already did one better: binfmt_misc (see https://blog.cloudflare.com/using-go-as-a-scripting-language... for using it for a much cleaner way of using it to use gorun on executable *.go files.)