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 :)

Subscribe now

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:

The old python language logo
The Pythonus Originatus was a mostly bytivore species endemic in the Amsterdam ecosystem before it spread to the American continent in a contaminated tweed jacket.

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.

Back to the future, now. If you subscribe with your email here (or using RSS), you’ll get notified when tomorrow’s articles are available. It’s a revolution. From 20 years ago.

Author Of article : Bite Code! Read full article