Cześć.
Dziś luźny artykuł, bo dziś pobawimy się jedną z pierwszy wersji Pythona. Skompilujemy go i zobaczymy co tam w nim wtedy siedziało.
Krótko o historii
Python 1.0 był pierwsza „oficjalną” wersją Pythona. Wcześniej była tylko wersja 0.9 wydana w 1991 roku, która jednak wymagała dopracowania, ale finalnie, 15 lutego 1994 roku, po poprawkach, stała się wersją 1.0.1 (tak, do wersji 1.0 były jeszcze poprawki). Można przyjąć, że od tego momentu język zaczął się naprawdę sprawnie rozwijać. W 1995 roku mieliśmy już wersję 1.2, po drodze była jeszcze 1.6 aż w końcu, w roku 2000, wydana została wersja 2.0. Rok 2006 to już wersja 2.5 a 2009 to pierwsza, naprawdę poważna wersja 2.7, którą spora część z Was może jeszcze pamiętać. Co ciekawe, wsparcie do wersji 2.7 trwało aż do stycznia 2020. To naprawdę długo. Wynikało to z tego, że migracja pomiędzy Pythonem 2 a 3 wymagała zmian w kodzie. Pisząc ten artykuł mamy wersje 3.13.
Zobaczmy jak to się zaczynało.
Kompilacja Pythona 1.0
Na początek muszę wspomnieć, że skompilowanie tak starego kodu nie jest takie proste, bo na nowoczesnych Linuxach biblioteki zależne są znacznie nowsze niż wtedy, kiedy ten kod pisano. Więc trzeba się było trochę nagimnastykować. Żeby cokolwiek uruchomić na tym Pythonie napisałem Dockerfile’a:
1 2 3 4 5 6 7 8 9 10 | FROM feverch/debian-legacy:4 RUN apt-get update && apt-get install -y --force-yes wget build-essential gcc make libreadline-dev zlib1g-dev nano ARG package ADD $package ./ WORKDIR /python-1.0.1 RUN ./configure && make ENTRYPOINT [ "/bin/sh" ] |
Zauważ, że obraz budowany jest z starego Debiana. Paczka z samym Pythonem jest przekazywana jako argument, bo openssl w tak starym Debianie nie pozwalał ściągnąć pliku poprzez nowoczesne HTTPS. Trochę z tym walczyłem, ale skończyło się na tym, że napisałem dodatkowy kawałek basha:
1 2 3 4 5 6 7 8 9 10 | #! /bin/bash name=$1 echo "Building Python 1.0.1" if [ ! -f python1.0.1.tar.gz ]; then wget https://legacy.python.org/download/releases/src/python1.0.1.tar.gz fi docker build . -t python1 --build-arg package=python1.0.1.tar.gz docker run --rm -it -v "$PWD"/code:/code python1 ./python /code/"$name" |
Który ściąga Pythona 1.0.1, buduje obraz Dockera i uruchamia go z nazwą pliku do uruchomienia. Całość buduje się bardzo szybko i sprawnie. Zakładając, że pokazany wyżej plik bash będzie nazywał się „run_py1.sh” to uruchomienie skryptu pod Pythonem 1.0.1 będzie wyglądało tak:
./run_python-1.sh test.py
Gdzie plik „test.py” będzie szukamy w folderze o nazwie „code”. Folder musi znajdować się w zaraz obok tego pliku bash’owego.
Pobawmy się!
Na rozgrzewkę coś łatwego:
1 2 3 | import sys print sys.version |
Wynik jest do przewidzenia: 1.0.1 (Feb 12 2025), czyli wersja i data kompilacji. Warto zauważyć, że w Pythonie wcześniejszym niż 3, funkcja print była używana nie jak funkcja a jak słowo kluczowe, tj bez nawiasów, więc, żeby odróżnić ten kod od aktualnych wersji, będę używał printa bez nawiasów. Teraz taki kod by się nie skompilował.
To teraz coś bardziej skomplikowanego:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | f = open("test.csv", "w") data = [ ["r1c1", "r1c2"], ["r2c1", "r2c2"], ["r3c1", "r3c2"] ] for x in data: f.write(x[0] + ", " + x[1] + "\n") f.close() f = open("test.csv", "r") for x in f.readlines(): print x |
Też działa! ALE, zapis tego samego kodu z użyciem context manager już powoduje SyntaxError. Co ciekawe, to rezultat jest dosłownie tylko taki. Czyli tylko „SyntaxError” bez pokazania gdzie dany błąd się znajduje. Plus brak nowej linii na końcu pliku też powoduje SyntaxError. Czasem. A czasem działa ok.
Idźmy dalej!
1 2 3 4 5 6 7 | import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('www.mmazurek.dev', 80)) request = "GET / HTTP/1.0\r\nHost: www.mmazurek.dev\r\n\r\n" s.send(request) data = s.recv(1024) print data |
Sockety też działają!
Natomiast coś chyba średnio skompilowałem, bo
1 | import time |
rzuca błąd „Segmentation fault (core dumped)”. Jest to dość ogólny błąd pamięci, który wskazuje na próbę uzyskania dostępu do pamięci do której nie ma się dostępu (albo robi to się w niewłaściwy sposób). Ogólnie każdy kto pisał coś z C/C++ na pewno kiedyś ten błąd widział. Przykład programu w C++, którego efekt jest Segmentation fault:
1 2 3 4 5 6 7 8 9 10 11 12 | #include <iostream> #include <vector> int main() { std::vector<int> vec; std::cout << "Zaraz wystąpi błąd segmentation fault..." << std::endl; // Próba dostępu do elementu poza zakresem wektora std::cout << vec[1000000] << std::endl; return 0; } |
Czekaj, stop!
Podoba Ci się to co tworzę? Jeśli tak to zapraszam Cię do zapisania się na newsletter:Jeśli to Cię interesuje to zapraszam również na swoje social media.
Jak i do ewentualnego postawienia mi kawy :)
A jak wydajność?
Zabawy za nami, więc zobaczmy jak wygląda to pod względem wydajności. Wygenerowałem kawałek kodu w LLMie, który pomoże nam zobaczyć różnice:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | import math def prime_numbers(limit): primes = [] for num in range(2, limit): is_prime = 1 for div in range(2, int(math.sqrt(num)) + 1): if num % div == 0: is_prime = 0 break if is_prime: primes.append(num) return primes def cpu_stress_test(iterations, size): result = [] for _ in range(iterations): primes = prime_numbers(size) result.append(primes) cpu_stress_test(5000, 1000) |
I odpalamy!
Wersja Pythona | Czas uruchomienia |
1.0 | 7.75s |
3.6 | 4.37s |
3.13 | 2.22s |
3.13 + JIT (link do artykułu, jeśli nie wiesz czym jest JIT) | 2.01s |
PyPy 3.10-7.3.17 | 672.12 ms (!) |
Jak widać różnice są ogromne. Testy robiłem systemową funkcją time, nie są one ani trochę rzetelne, nie mniej jednak pokazują różnice.
Zmierzając do końca
Jak widać, Python na swojej drodze do zostania jednym z najpopularniejszych języków programowania, przeszedł wiele zmian, optymalizacji i unowocześnień, coraz lepiej odpowiadając na potrzeby swoich użytkowników. Jestem przekonany, że twórcy nie powiedzieli jeszcze ostatniego słowa.
Mateusz Mazurek