Summary
With a little help from podman, we can compile the first major stable version of Python. It’s surprisingly modern already, with high-level data structures and all you need to work with processes, text, files, and the network. But it does have some funny quirks :)
Spared no expense
The last 27th of January, Python turned 31 years old, and you saw a bunch of social media posts "celebrating" this fact, because it's a cheap, easy way to create engagement you can repeat every year.
But let's have fun and see what Python really looked like when the first stable release came out.
And by fun I mean let's waste time trying to figure out how to compile this fossil:
First, you need to find the sources, and you obviously won't find them in the download section of python.org. In fact, not even in the old, deprecated, but still online, FTP repo. Still, there is the little unknown legacy.python.org and low and behold, we can start digging there.
Now, of course, this won't compile on my shining Ubuntu 24.04.
But we are so lucky to live in a world of commoditized virtualization, and we have containers for everything. Let's pop an old debian with podman, and see what's up:
❯ podman pull docker.io/feverch/debian-legacy:4
Trying to pull docker.io/feverch/debian-legacy:4...
Getting image source signatures
Copying blob 45e6962a03f5 done |
Copying config 960085cd72 done |
Writing manifest to image destination
960085cd72a37b37062d5fc6b082fafc0afd12917071d8b27110d6d0d64eebc2
❯ podman run -it docker.io/feverch/debian-legacy:4 /bin/bash
78b596c7cf8a:/tmp/python-1.0.1# cat /etc/issue
Debian GNU/Linux 4.0 \n \l
And just like that, we are back into the Jurassic era, or as I like to call it, "when I was young". Not that I'm old, but I told my little bro I was still young, and he fired back stating "No young people say they are still young". The little brat.
He got a point though, because we can't use apt
here, we have to use apt-get
, to install a whole bunch of compilation tools:
apt-get update && apt-get install -y wget build-essential gcc make libreadline-dev zlib1g-dev
Thank, Ian Murdock, our Lord, for Debian's stability, as those packages are pretty much the same as today’s.
Anyway, time to download the sources:
78b596c7cf8a:/# wget https://legacy.python.org/download/releases/src/python1.0.1.tar.gz
--12:39:37-- https://legacy.python.org/download/releases/src/python1.0.1.tar.gz
=> `python1.0.1.tar.gz'
Resolving legacy.python.org... 167.99.21.118, 159.89.245.108
Connecting to legacy.python.org|167.99.21.118|:443... connected.
OpenSSL: error:1407742E:SSL routines:SSL23_GET_SERVER_HELLO:tlsv1 alert protocol version
Unable to establish SSL connection.
Ah, yes, it won't work. Most modern TLS systems reject obsolete crypto. And I'm not going to compile a more modern wget
and openssl
version to get over that, I'm not that masochistic.
So outside the container, I wget https://legacy.python.org/download/releases/src/python1.0.1.tar.gz
, then podman will nicely copy it into the running pod:
❯ POD_ID=$(podman ps | grep debian | cut -d" " -f1)
❯ podman cp python1.0.1.tar.gz "${POD_ID}:/tmp"
Now back into our container, a little bit of "tar eXtract Ze File":
78b596c7cf8a:/# cd /tmp/
78b596c7cf8a:/tmp# tar xzf python1.0.1.tar.gz
78b596c7cf8a:/tmp# cd python-1.0.1/
Then the thing will compile in a few seconds:
78b596c7cf8a:/# ./configure
checking for python to derive installation directory prefix
chose installation directory prefix
checking for gcc
checking for install
checking for ranlib
checking for ar
checking for AIX
...
checking for Xenix
creating config.status
creating Makefile
creating Objects/Makefile
creating Parser/Makefile
creating Python/Makefile
creating Modules/Makefile.pre
creating config.h
78b596c7cf8a:/# ./make
(cd Modules; make -f Makefile.pre Makefile)
make[1]: Entering directory `/tmp/python-1.0.1/Modules'
cp ./Setup.in Setup
/bin/sh ./makesetup Setup
...
gcc -O -I./../Include -I.. -DHAVE_CONFIG_H -DPYTHONPATH=\".:/usr/local/lib/python:/usr/local/lib/python/test\" -c config.c
gcc config.o libModules.a ../Python/libPython.a ../Objects/libObjects.a ../Parser/libParser.a -ldl -lm -o python
mv python ../python
make[1]: Leaving directory `/tmp/python-1.0.1/Modules'
That's it. That's actually it. We can now raise our reptile from the dead (incidentally, it's weird that no necromancer does that to raptors in traditional lores, it would be really badass):
./python
Python 1.0.1 (Feb 5 2025)
Copyright 1991-1994 Stichting Mathematisch Centrum, Amsterdam
>>> print "Hello"
Hello
Clever girl
Let's see how smart it was:
>>> help()
Traceback (innermost last):
File "<stdin>", line 1
NameError: help
Hum... I'm on my own.
>>> class Foo():
SyntaxError
>>> def add(a, b):
... return a + b
...
>>> add(1, 2)
3
No classes at the time, but functions already looked like Python. Oh, and it’s a live REPL. We take it for granted, but it was a nice QoL, and not universal.
A lot of good data structures as well:
>>> []
[]
>>> 1, 2, 3
(1, 2, 3)
>>> {}
{}
>>> set() # but no set :)
Traceback (innermost last):
File "<stdin>", line 1
NameError: set
Error handling is already using exceptions, albeit a barebone version:
>>> raise Exception('Ah ah')
Traceback (innermost last):
File "<stdin>", line 1
NameError: Exception
>>> raise "Ah ah"
Traceback (innermost last):
File "<stdin>", line 1
Ah ah
>>> try:
... 1/0
... except:
... print('welp')
...
welp
However, compared to the standard for scripting at the time, it’s pretty sweet.
If I exit the shell and look around, there is a lot in the stdlib:
78b596c7cf8a:/tmp/python-1.0.1# ls Lib/
Makefile bdb.py codehack.py dospath.py getopt.py maccache.py nntplib.py persist.py rand.py sched.py stdwin tb.py tzparse.py
Queue.py bisect.py colorsys.py dump.py glob.py macpath.py os.py pipes.py regex_syntax.py sgi string.py tempfile.py util.py
UserDict.py calendar.py commands.py emacs.py grep.py mimetools.py ospath.py poly.py regexp.py shutil.py sun4 test wave.py
UserList.py cmd.py dircache.py fnmatch.py imghdr.py multifile.py packmail.py posixpath.py regsub.py sndhdr.py sunau.py toaiff.py whatsound.py
aifc.py cmp.py dircmp.py fpformat.py importall.py mutex.py pdb.doc profile.doc repr.py stat.py sunaudio.py token.py whrandom.py
audiodev.py cmpcache.py dis.py ftplib.py linecache.py newdir.py pdb.py profile.py rfc822.py statcache.py symbol.py tokenize.py zmod.py
You already got getopt
for arg parsing, regexp support, ftplib
to share warez and even pdb!
But I can't import most of it in the shell:
>>> import os
Traceback (innermost last):
File "<stdin>", line 1
ImportError: No module named os
However, I can force it by adding it to the PYTHONPATH
(oh, the irony of dep problems and import path hacks!) when starting said shell:
78b596c7cf8a:/tmp/python-1.0.1# PYTHONPATH="Lib" ./python
And now, I can python properly:
>>> import os, sys
Well, more or less:
>>> import time
Segmentation fault (core dumped)
Maybe I miscompiled something.
Still, you can already deal with most things in your environment, like spawning processes, or dealing with files, which on Unix is enough to do pretty much anything:
>>> import os, sys
>>> os.environ
{'PS1': '\\h:\\w\\$ ', 'OLDPWD': '/tmp', 'SHLVL': '1', 'HOSTNAME': '78b596c7cf8a', 'container': 'podman', 'HOME': '/root', 'PATH': '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin', 'PWD': '/tmp/python-1.0.1', 'PYTHONPATH': 'Lib', '_': './python', 'TERM': 'xterm'}
>>> os.listdir('/tmp')
['.', '..', 'python1.0.1.tar.gz', 'python-1.0.1']
>>> os.system('ls .')
ChangeLog Ext-dummy Lib Misc Parser acconfig.h config.status python
Demo Grammar Makefile Modules Python config.h configure readline
Doc Include Makefile.in Objects README config.h.in configure.in
0
>>> f = open('/tmp/foo', 'w')
>>> f.write("It's a live")
>>> f.close()
>>> import sys
>>> sys.version
'1.0.1 (Feb 5 2025)'
>>> sys.argv
['']
And the network works too:
>>> import socket
>>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> s.connect(('www.python.org', 80))
>>> request = "GET / HTTP/1.0\r\nHost: www.python.org\r\n\r\n"
>>> s.send(request)
>>> data = s.recv(1024)
>>> print(data)
HTTP/1.1 301 Moved Permanently
Connection: close
Content-Length: 0
Server: Varnish
Retry-After: 0
Location: https://www.python.org/
Accept-Ranges: bytes
Date: Wed, 05 Feb 2025 13:36:22 GMT
Via: 1.1 varnish
X-Served-By: cache-par-lfpg1960049-PAR
X-Cache: HIT
X-Cache-Hits: 0
X-Timer: S1738762583.573696,VS0,VE0
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
So even on the very first version, it was quite a capable little language. I would even say that at the time, given it had less competition from alternatives, it felt probably more powerful than today when your choices were between more or less Lisp, Perl, C and bash for small programs.
>>> Segmentation fault (core dumped)
LOL! Boy, does that bring back some memories. I don't think I've ever experienced this problem using (modern) Python.
A very fun read. Thanks for the effort.