PHP i Python – porównanie

Poza moimi głównymi zainteresowaniami, czyli językami hipertekstowymi i stylami kaskadowymi to właśnie PHP jest ciągle najważniejszym elementem mojej pracy. Jednak widząc braki tego języka przez ostatnie kilka lat postanowiłem przejść pod skrzydła bardziej nowoczesnych technologii. Nie zamierzam wcale zmieniać utartej opinii PHP jako języka łatwego w nauczeniu, a zarazem ograniczonego w możliwościach, bo jest w tym sporo prawdy. Tak więc miałem wybór pomiędzy Pythonem a Ruby. Co w tej chwili wybrałem, pewnie się domyślacie.

Kwestia wyboru była dość łatwa, w końcu P jest w alfabecie wcześniej niż R, ale poważnie mówiąc znałem kilka osób które używały już Pythona, a z Rubym nie było już tak kolorowo. Dodatkowo argument, że tego języka używa Google nie był wcale decydujący. W takim razie czemu podoba mi się Python, a w dalszej części samo django.

Python jest językiem wieloparadygmatowym. Pozwala jednocześnie na programowanie obiektowe, strukturalne i funkcyjne. Posiada dynamiczne sprawdzanie typów i wbudowane zarządzanie pamięcią poprzez garbage collector. Składnia Pythona, choć na początku oszczędna i może trudna w ogarnięciu, z czasem okazuje się czytelna i potężna.

Same typy są silnie powiązane z systemem klas, choć w Pythonie nie ma enkapsulacji. Możliwa jest instrospekcja obiektów, więc programista ma szeroki dostęp do atrybutów obiektu. Funkcje i klasy po opatrzeniu dokumentacją w kodzie źródłowym, mają do niej dostęp w czasie wykonywania programu.

Wbudowane typy danych

Python posiada szerszy wachlarz wbudowanych typów danych, chociaż gdy spojrzy się na niego tak bardzie dokładnie to nie jest ich, aż tak więcej niż w samym PHP. Poniższa tabela przedstawia istniejące typy danych dla PHP.

Dostępne typy danych w PHP
Typ Opis Przykład
int liczba całkowita 495, 123
double liczba zmiennoprzecinkowa 3.1415, 123.0
bool prawda lub fałsz true, false
null nic null
string ciąg znaków „PHP jest fajne.”, ‚nie każdy to wie’
array tablica array(1, 2), array(‚a’ => 1)
object obiekt egzemplarz zdefiniowane klasy
resource zasób deskryptor, połączenie z bazą danych, itp.

PHP nie posiada odrębnego typu liczby zmiennoprzecinkowej, krótkiej i długiej jak to było choćby w C, czyli float i double. Nie przytaczam tu zakresu wartości jakie przyjmuje zmienna określonego typu. Z kolei tablica w PHP jest indeksowaną kolekcją wartości, indeksowaną numerycznie lub asocjacyjnie.

Poniżej znajdziesz odpowiednią tabelę dla Pythona.

Wybrane typy danych w Pythonie
Typ Opis Przykład
int (oraz long w Python 2) liczba całkowita 495, 123
float liczba zmiennoprzecinkowa 3.1415, 123.0
complex liczba zespolona 3+2.7j
bool prawda lub fałsz True, False
None nic None
Python 3: str
Python 2: unicode
tekst w Unicode (niezmienny) Python 3: ‚Napis’ lub „Napis”
Python 2: u’Napis’ lub u”Napis”
Python 3: bytes
Python 2: str
tekst w ASCII (niezmienny) Python 3: b’Napis’ lub b”Napis”
Python 2: ‚Napis’ lub „Napis”
list lista (zmienna zawartość i długość) [4.0, ‚tekst’, True]
tuple krotka (niezmienna) (4.0, ‚tekst’, True)
set zbiór (zmienny) Python 3: {4.0, ‚tekst’, True}
Python 2: set([4.0, ‚tekst’, True])
frozenset zbiór (niezmienny) Python 3: frozenset({4.0, ‚tekst’, True})
Python 2: frozenset([4.0, ‚tekst’, True])
dict słownik (zmienny) {‚key1’: 1.0, 3: False}

Kwestia typów danych w Pythonie wydaje się skomplikowana na pierwszy rzut oka. Chociażby obsługa ciągów znaków, które istnieją w kilku wersjach zależne od kodowania. Prawdziwą zaletą Pythona jest wsparcie dla Unikodu, czego niestety nie uświadczy się w PHP. Prawdą jest, że istnieją metody konwersji, również dla Unicode, ale to jeszcze nie to. Zobaczymy w PHP6.

Napisy w obu językach są niezmienne, czyli wywołanie funkcji (PHP) lub metody (Python) zmieniającej wielkość liter zwraca nowy ciąg znaków bez modyfikacji oryginalnego.

Kolekcje w Pythonie również wydają się trudne do zrozumienia, ale to tylko pierwsze wrażenie. Listy, krotki i ciągi znaków są sekwencjami. Wszystkie udostępniają iterowanie lub dostęp poprzez indeks. Lista to tablica o zmiennej liczbie elementów, które można dodawać, modyfikować i usuwać. Wspomniane operacje są niedostępne dla krotek o stałej liczbie elementów.

Kolekcje nieuporządkowane to słowniki i zbiory. Prościej mówiąc, słownik w Pythonie to tablica asocjacyjna w PHP. Kluczami słownika nie mogą być elementy zmienne, czyli listy lub zbiory zmienne. Mogą zaś krotki i zbiory niezmienne, o ile zawierają wyłącznie elementy niezmienne. Dzięki temu kolekcje są bardzo wydajne podczas operacji przeszukiwania, sortowania, itp.

Pozostałe standardowe typy nie budzą wątpliwości, nawet None zostanie prawdopodobnie właściwie zinterpretowany przez początkującego programistę.

Rzutowanie

Zarówno PHP jak i Python są językami o dynamicznym typowaniu zmiennych. Moim zdaniem PHP podchodzi do kwestii automatycznego rzutowania bardziej liberalnie i często udostępnia zadziwiające wyniki operacji dodawania lub konkatenacji dla zmiennych różnego typu. Na przykład łączenie ciągów znaków i liczby może dać "0", w określonym przypadku. Przy dodawaniu tych samych zmiennych można otrzymać liczbę 0 lub inną.

$liczba = 0;
$wyraz = "";

$laczenie = $liczba . $wyraz;
$dodawanie = $liczba + $wyraz;

var_dump($laczenie);
var_dump($dodawanie);

Operator łączenia, czyli . próbuje uzyskać ze zmiennej innego typu ciąg znakowy i wykonuje określoną operację.

string(1) "0"
int(0)

Analogicznie wygląda sytuacja z dodawaniem liczb, gdzie dany ciąg znakowy jest konwertowany do liczby, a przynajmniej następuje próba. Przykładowo takie dwie zmienne dają zaskakujące dla nieświadomej tego osoby wyniki w środowisku PHP.

$liczba = 12;
$wyraz = "8aaa";

string(6) "128aaa"
int(20)

Właśnie o tę próbę chodzi, bo nie zawsze pożądana jest konwersja, jeśli w ogóle możliwa.

$liczba = 12;
$wyraz = "aaa8";

string(6) "12aaa8"
int(12)

Python zwyczajnie w świecie nie pozwala na operacje między zmiennymi różnego typu.

liczba = 0
wyraz = ""
liczba + wyraz
Traceback (most recent call last):
  File "", line 1, in 
TypeError: unsupported operand type(s) for +: 'int' and 'str'

Aby taka operacja powiodła się konieczne jest jawne rzutowanie jednego z typów, w tym przypadku ciągu znaków.

liczba + int(wyraz)
Traceback (most recent call last):
  File "", line 1, in 
ValueError: invalid literal for int() with base 10: ''

Co dalej nie musi dać pożądanego wyniku, bo jak niby dodać 0 i ""? Wprawdzie zakładamy, że pusty ciąg znaków to liczbowe zero, ale Python takich założeń nie robi i to mi się właśnie w nim podoba.

wyraz = "1"
liczba + int(wyraz)
1

W Pythonie istnieje automatyczne rzutowanie dla typów numerycznych, co pozwala na operacje pomiędzy liczbą całkowitą i zmiennoprzecinkową, ale już sama liczba nie jest prawidłową wartością dla operacji łączenia ciągów znaków.

Kwestie niejawnego rzutowania zmiennych w PHP ujawniają się również w przypadku operacji warunkowych, gdzie takie zmienne mogą zupełnie nieoczekiwanie zwracają prawdę lub fałsz.

Zmienna liczba argumentów funkcji

Python pozwala na tworzenie funkcji ze zmienną liczbą argumentów. Dopuszcza także przekazywanie argumentów o wartościach domyślnych i argumentów nazwanych. Jeśli wydaje się to skomplikowane to najłatwiej przeanalizuj poniższy przykład.

def moje_argumenty(x, y, *args, **kwargs):
    print "Argumenty:"
    print "x=%s, y=%s" % (x, y)
    print "Argumenty pozycyjne:"
    for a in args:
        print a,
    print
    print "Argumenty nazwane:"
    for k in kwargs:
        print "%s=%s" % (k, kwargs[k]),
    print

Zdefiniowana funkcja drukuje na ekran argument jakie zostały przesłane podczas wywołania. Wywołanie funkcji moje_argumenty('abc', 123, 456, 'def', k=789, m='ghi') da następujący rezultat:

Argumenty:
x=abc, y=123
Argumenty pozycyjne:
456 def
Argumenty nazwane:
k=789 m=ghi

PHP mimo swoich wszystkich zalet jest w tej kwestii ograniczone. Prezentowana poniżej definicja umożliwia ustawienie wartości domyślnych argumentów.

def moje_argumenty($x, $y, $z='xyz'):
    print "Argumenty:";
    print "x=$x, y=$y, z=$z";

Podczas wywołania funkcji moje_argumenty istnieje kilka możliwości zachowania się PHP, w zależności od liczby argumentów.

moje_argumenty('abc', 123);
moje_argumenty('abc', 123, 'aaa');
moje_argumenty('abc', 123, 456, 789);
moje_argumenty('abc');

Na początku do funkcji trafia 2 argumenty, ale dostępny jest również trzeci o wartości 'xyz', domyślnie określony w definicji funkcji. W drugim przypadku trzeci argument został przekazany, zatem jego domyślna wartość z definicji funkcji nie obowiązuje. Trzeci przypadek jest podobny, mimo przekazania większe ilości argumentów niż określono w sygnaturze. Jedynym błędnym wariantem jest zbyt mała liczba argumentów, czyli wywołanie czwarte, choć z taką sytuacją nie poradzi sobie także Python.

PHP Warning:  Missing argument 2 for moje_argumenty(), called in ...

Traceback (most recent call last):
  File "", line 1, in 
TypeError: argumenty_funkcji() takes at least 2 arguments (1 given)

Całkowicie zrozumiała jest sytuacja powodująca błąd, gdy jawnie określa się argument funkcji, a potem nie ustawia przy wywołaniu.

Wcięcia

Bloki instrukcji w PHP, podobnie jak w innych klasycznych językach oznacza się poprzez { i }, czyli nawiasy klamrowe. Dobrą praktyką programistyczną jest również używanie wcięć do zaznaczenia hierarchii kodu. Python nie używa ani nawiasów klamrowych, ani innych słów kluczowych do oznaczenia bloków, a jedynie wcięć.

Przykład funkcji w PHP, która oblicza silnię:

function silnia($x) {
    if ($x == 0) {
        return 1;
    } else {
        return $x * silnia($x-1);
    }
}

To samo w Pythonie:

def silnia(x) {
    if x == 0:
        return 1;
    else:
        return x * silnia(x-1);

Przykład kodu PHP zadziała nawet bez nawiasów w instrukcji warunkowej, bo wykonywana jest jedna instrukcja. Jednak w przypadku Pythona ilość instrukcji nie ma znaczenia bo samo wcięcie oznacza blok. Nie zaprzeczam, że bardzo mi się to podoba.

Programowanie funkcyjne

Bardzo częstymi operacjami podczas programowania są różne manipulacje dla tablic. Python wspiera programowanie funkcyjne, takie jak w Haskellu, co znacznie ułatwia obróbkę kolekcji. Przykładowo chcesz uzyskać kwadraty danych liczb, co w PHP wymaga pętli po zdefiniowane tablicy:

$liczby = array(1, 2, 3, 4, 5);
foreach ($liczby as $n) {
    $kwadraty[] = $n*$n;
}

Analogiczny problem w Pythonie rozwiąże jedna linijka, z definicją liczb to dwie:

liczby = [1, 2, 3, 4, 5]
kwadraty = [n**2 for n in liczby]

Gdyby konieczne było wykonanie dwóch przebiegów pętli po indeksach różnych tabel to powyższy zapis jest nieoceniony. Pomijam sam operator, bo n**2 jest równoznaczne n*n.

Generatory

Wcześniejszy przykład z definiowaniem tablicy liczb jest jak najbardziej odpowiedni. Wprawdzie PHP ma dostępną funkcję range(), ale to nie wszystko, jeśli chodzi o generatory. Uzyskanie takiej tablicy w PHP jest proste:

$liczby = range(1, 5);

print_r($liczby);
Array
(
    [0] => 1
    [1] => 2
    [2] => 3
    [3] => 4
    [4] => 5
)

Możliwe jest uzyskanie wartości z danego zakresu, z uwzględnieniem odpowiedniego kroku. Na przykład range(1, 5, 2).

Array
(
    [0] => 1
    [1] => 3
    [2] => 5
)

Co więcej, PHP wygeneruje tablice liter i liczb (w tym ujemnych), w rosnącym lub malejącym porządku. Wydaje mi się w tej chwili, że ten aspekt w PHP jest lepiej przygotowany.

Python potrafi wprawdzie wygenerować taką samą tablicę poprzez rozwinięcia list lub samą funkcję range(), ale to chyba kwalifikuje się do punktu powyżej.

[x + 1 for x in range(5)]
[1, 2, 3, 4, 5]

range(1,6)
[1, 2, 3, 4, 5]

Dodatkowo od Pythona 2.4 pojawiły się wyrażenia generatorowe, które stosują mechanizm leniwej ewaluacji (ang. lazy evaluation). W przypadku generowania dużej struktury danych zawierającej od razu wszystkie obiekty, wyrażenie generatorowe działa na pojedynczym obiekcie, dzięki czemu oszczędza pamięć. Python 3 uznaje rozwinięcia list jako konstrukcje przestarzałe.

Operatory logiczne

W Pythonie kilkanaście elementów traktuje się jako fałsz. Między innymi liczby zero: 0, 0.0, 0, False, None, puste napisy i kolekcje. Obiekty posiadające metodę __nonzero__() w Pythonie 2, jeśli zwraca False lub 0, a także obiekty posiadające metodę __bool__(), jeśli zwraca False są logicznym fałszem. Wszystko inne jest prawdą.

Wartości zwracane przez operatory porównania, operator zawierania i negacji są reprezentowane przez True i False. Dodatkowo Python umożliwia łączenie operatorów porównania, które interpretuje się wówczas jak zapis matematyczny.

Wyjątki

Python bardzo intensywnie korzysta z wyjątków jako metody wykrywania błędów. Dodatkowo sugerowana jest metoda „osiągania celu” niż „zapobiegania błędu”. Według ten koncepcji najpierw następuje próba osiągnięcia celu, a jeśli zwróci błąd, obsłużenia tej sytuacji. Inaczej niż w PHP, które raczej sprawdza czy nie wystąpi błąd, zanim cokolwiek zrobi.

Podsumowanie

Podoba mi się tryb interaktywny Pythona, w którym od razu otrzymuje wynik mojego kodu. Kocham wykrojenia sekwencji, jak również metody dla określonych typów danych. Wiele efektów w Pythonie osiąga się bardzo łatwym sposobem i tanim kosztem, jak chociażby operacje na drzewie katalogów. Sposób dołączania kolejnych modułów czy całych pakietów jest intuicyjny. Praca z ciągami znaków, które są sekwencjami jest naprawdę przyjemna. Dodatkowo istnieje dobra dokumentacja i życzliwa społeczność tego języka.

Wiem, że Python wspiera jeszcze inne rzeczy, o których nie wspomniałem, między innymi testy jednostkowe, ale to temat na kolejny wpis. Jeśli myślisz o nowym języku programowania, a zajmujesz się aplikacjami internetowymi lub administrowaniem serwerami to może akurat Python.

3 odpowiedzi do “PHP i Python – porównanie”

  1. Ja się właśnie przymierzam do Pythona. Tzn przymierzam się od dłuższego czasu, ale jakoś ciągle brakuje mi motywacji, żeby przysiąść i się zabrać za niego na poważnie.

    Ech – mam nadzieję, że jak się uporam z tym, co mam do wykodzenia w PHP, znajdę kilka dni dla Pythona :) Pozdrawiam!

  2. Ja się tak przymierzałem dobry rok, ale jak już wsiąkłem w Pythona to naprawdę pisało się przyjemnie. Wymaga trochę cierpliwości od programistów, szczególnie dinozaurów C/C++.

  3. Pisanie w Py to czysta przyjemność :)

    Odkąd programuję w Py (2 lata), znienawidziłem {}, chyba że tworzę słownik :D

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *