Python is a bit broken

Wed Dec 14 2022E.W.Ayers

For the last few years I've been maintaining a list of things that I don't like about Python. For most of them, I have now seen the light and the main problem was that I was not being 'Pythonic'. There are still some remaining gripes so I am dumping them here.

I recently found a Twitter thread of a person I follow struggling with Python. Python is supposed to be the friendly, easy language and it's a shame that its this clunky.

1. Setting up a Python environment is fragmented and confusing

1.1. Installing python

To start with, just look at the Homebrew entry for Python. It is a three-page list of caveats and recommendations. Interpreter version management comes across as an unsolved problem.

Version 2.7 and 3.x are not compatible with each other. This would be fine except that they are both still used and there is no single way to make sure that you are using the right version (for example, for a new install / image, it is a coin flip whether typing python in the shell will launch 2.7 or 3) Further, if you forget you can use pip instead of pip3 and then have a cycle of headscratching figuring out why your module doesn't work.

Ok, maybe instead we should use this thing called conda I keep hearing about, maybe that will make it easy.

Another option - that I ultimately ended up sticking with - is PyEnv. Pyenv seemed to be the

How to fix it? The core Python team should adopt PyEnv or roll their own version manager, similar to rustup for the Rust compiler.

1.2. Python packaging is obtuse and fragmented

Do you know the difference between virtualenv, pyenv, pyenv-virtualenv, virtualenvwrapper, pyenv-virtualenvwrapper, pipenv, pyvenv, venv pip, pip3, conda, miniconda, anaconda, easy_install, setuptools, poetry, hatch, hatchling, flit, twine, tox, nox, distutils? All of these help you manage packages, versioning and environments in some way. This SO answer differentiates some of them, see also this list. If you try using multiple of these tools, you risk putting your environment in an inconsistent broken state with lots of scary error messages. For a newcomer to programming, this makes Python borderline unusable. Having a fragmented set of third-party tooling for something as fundamental as package management is sad.

How to fix it? the Python core and PyPI team make and sanction a canonical CLI tool for package and environment management (like rustup for Rust). I vote add it in to pip. You can keep using your old random third-party tooling, but this is the standard way going forward.

Instead, the solution seems to be to make an abstraction over packaging programs (PEP 517 and PEP 621, PEP 631, PEP 660) so that they can all live together. This is a mistake because the space is still fragmented and confusing, and third-party tools will not obey the spec if it impedes functionality.

Also, all package writers need to be able to keep their version number in one place. So the official docs recommend 7 convoluted ways of doing that. The first idea in the list is text-scraping a python file.

1.3. How I solve this

I solve this problem by using PyEnv, venv, pip and nothing else. For packaging projects I still haven't found a good solution, I am having a go at using hatch and report back.

2. Python's module resolution is annoying

Meanwhile, importing the modules that you want to use breaks if you go slightly off what is deemed pythonic.

If your project has the form

(1)
~/my_project
/module1
/__init__.py
/fizz.py
/module2
/__init__.py
/buzz.py

You get told off for trying to import something from module1 from buzz.py? There is an SO post titled Relative Imports for the Billionth Time that portrays how infuriating this is. You are only allowed to relative import things within 'packages'. Why? Most people coming to Python don't know what a package is!

Python is supposed to be this easy and dynamic programming language, but you can't import another file without doing a song-and-dance with __init__.py and managing the python path? Maybe it's a security thing. I need to get to the bottom of this.

3. Other gripes

3.1. Closures are weird

Can you predict what this code should do?

(2)
l = [lambda: print(n) for n in ['a', 'b']]
for f in l:
f()

It prints 'b,b' because in python closures are messed up. The same mutable variable n is passed to both functions. The same code in JavaScript gives a, b.

Explanation.

3.2. Default values are weird

Contributed by George.

(3)
def func(x = []):
x.append("hi")
return x
func()
print(func())

The default value of x is not created anew for each invocation of func, it's a static value that is shared among all invocations. This is a good footgun.

3.3. Wishes