4 edycja
warsztatów zakończona

Wstęp do warsztatów - PHP vs Python, podstawy

Published: Tue 21 October 2014
Author: Piotr Mazurek
tags: python php

Już w listopadzie startują warsztaty z podstaw Pythona dla programistów PHP w ramach akcji Python Has Power. W związku z tą akcją zapraszamy nie tylko na zajęcia, ale także do lektury i praktycznej nauki już teraz. Dziś proponuję porównanie obu języków, czyli PHP oraz Pythona. Pierwszą rzeczą, którą należy zaznaczyć jest to, że porównanie nie ma na celu wykazania wyższości żadnego z tych języków, jedynie zaciekawienie Ciebie nowymi możliwościami.

Różnice w podejściu

Tematem przewodnim artykułu jest uwidocznienie kilku ciekawych różnic oraz interesujących funkcjonalności i zainteresowanie Ciebie Pythonem i perspektywą pracy w tym języku.

W poniższym artykule zawartych jest kilka tematów które zostaną bardziej szczegółowo omówione na przeprowadzanych przez nas warsztatach (szczegóły na stronie www). Starałem się pokazać różnice w tych miejscach, w których nie spodziewasz się ich wcale. Sprawdź czy powiedzenie “lepsze jest wrogiem dobrego” aby na pewno ma zastosowanie w praktyce!

Nie raz programując, pewnie zdarzyło Ci się pomyśleć, że to fajnie, jak by zmienne były współdzielone pomiędzy dwoma niezależnymi od siebie requestami. Część początkujących programistów PHP nawet tak myśli. Jest to dosyć często pojawiający się błąd myślowy. Można tutaj wykazać bardzo ciekawą różnicę wynikającą z samego podejścia do serwowania stron w tych dwóch językach. W PHP jest to bowiem wykonane w taki sposób, iż gdy do serwera przychodzą żądania od użytkowników, całość kodu wykonywana jest osobno. Na co chcę zwrócić uwagę, to to, że sposób przetwarzania dwóch osobnych requestów jest od siebie całkiem niezależny i natywnie pozbawiany wspólnego stanu.

W Pythonie podejście do serwowania stron jest nieco inne. Z tytułu tego, iż Python jest językiem który z założenia nie ogranicza się do żadnego konkretnego zastosowania, można w nim również napisać bardzo sensowny serwer HTTP. I to też właśnie się dzieje. Każdy ze współczesnych webowych frameworków pythonowych zawiera w sobie własny serwer HTTP. Proszę zwrócić uwagę na subtelną różnicę - serwowanie aplikacji PHP odbywa się poprzez routowanie requestów przez już gotowy serwer HTTP (np. Apache) do konkretnego pliku PHP, który następnie rusza interpreter PHP, który z kolei czyta wszystkie niezbędne części aplikacji od początku do końca, aż do finalnej obsługi requestu. W Pythonie natomiast serwer po uruchomieniu czeka z już załadowanymi wszystkimi funkcjami, metodami i klasami i po przyjściu żądania odpala nowy wątek, na podstawie danych zawartych w nagłówku decydując o tym, która funkcja powinna obsłużyć dany request. Daje nam to nie tylko ciekawą różnicę w podejściu, ale również i fajne możliwości. Jedną z nich jest wspomniane przeze mnie wcześniej w formie zajawki współdzielenie zmiennych globalnych pomiędzy requestami, ale pozwolę sobie zauważyć jeszcze raz - Python nie ogranicza się do zastosowań webowych. Jeżeli więc chcesz, aby Twoj serwer w reakcji na request wykonał jakąś bardziej wymagającą pracę, pobrał asynchronicznie jakieś dodatkowe dane i wykonał na nich obliczenia, a następnie poinformował użytkownika o sukcesie (bądź niepowodzeniu :)) poprzez socket.io - proszę bardzo. Być może kręciłaby Cię możliwość napisania hobbystycznie interfejsu webowego dla obsługi robota zrobionego na Arduino? Nie musisz stosować żadnych dodatkowych sztuczek, czy też "protez" na język programowania! :) Ogranicza Cię tutaj tylko Twoja twórczość.

Wracając do wspomnianego wcześniej przykładu, świetnie nadaje się do tego pythonowy mikroframework webowy: Flask. Weźmy pod lupę poniższy kod:

[PYTHON]

from flask import Flask
app = Flask(__name__)

counter = 0
@app.route("/")
def hello():
    global counter
    counter += 1
    return "Current count: %d" % counter

if __name__ == "__main__":
    app.run()

Jak zapewne się domyślasz, funkcja "hello" za sprawą dekoratora @app.route('/') odpowiada na żądanie do głównego katalogu naszej domeny.. Zwróć uwagę na definicję zmiennej counter. Jest ona zdefiniowana poza ciałem funkcji, a wewnątrz używamy słowa kluczowego global, które zapewne znasz i domyślasz się jak działa. Każdy PHPowiec zapytany o to, jak zachowa się powyższy kod odpowie bez wahania: Zawsze będzie zwrócone "Current count: 1". A tutaj Pythonowa niespodzianka, z każdym kolejnym wejściem na stronę prezentowany będzie numer o jeden większy. Więcej nawet - gdy requesty robić będą dwaj niezależni od siebie użytkownicy, również każdy z nich będzie dostawał kolejną inkrementację, a nie, jak mogłoby się wydawać, liczby po kolei od 0.

Wszystko jest obiektem

Kolejną sprawą, która mnie osobiście bardzo zainteresowała jest to, że w Pythonie wszystko jest obiektem. I pisząc wszytko, wcale nie mam na myśli jakiejś przenośni, czy może półśrodku. Funkcja? Obiekt! Moduł? Obiekt! Ciąg znaków? Też obiekt. Świetnym przykładem może być to, iż na każdej funkcji, którą napiszemy możemy użyć atrybutu __doc__, która zwróci nam napisaną do danej funkcji dokumentację. Może to być dla Ciebie mylące - używamy atrybutu na funkcji?! Tak, atrybutu. Ponieważ funkcja, którą tworzysz jest obiektem, który następnie można zbadać, przypisać do zmiennej, przekazać jako argument, czy również modyfikować w locie. W praktyce wyglądało by to mniej więcej tak:

[PYTHON]

>>> def awesome_function(super_param):
...     """This is a SuperDocumentation"""
...     print "Am I an object?"

# Wywołanie funkcji
>>> awesome_function("Useless param!")
'Am I an object?'

# Zwracamy uwagę na brak nawiasów!
# dostajemy referencje na funkcję
>>> awesome_function
<function awesome_function="" at="" 0x1a02e60=""> # Pomieramy atrybut __doc__ funkcji którą właśnie zdefiniowaliśmy!
>>> awesome_function.__doc__
'This is a SuperDocumentation'

# funkcja dir pokazuje
# jakie metody i atrybuty są dostępne w danym obiekcie
>>> dir(awesome_function)
['__call__', '__class__', '__closure__', ... , 'func_name']

# dir z funkcji dir :)
>>> dir(dir)
['__call__', '__class__', '__cmp__', ' ... , '__subclasshook__']

# We need to go deeper!
>>> dir(dir(dir))
['__add__', ... , 'insert', 'pop', 'remove', 'reverse', 'sort']
# mały disclamer do powyższego - dir jest obiektem,
# natomiast wywołanie dir() zwraca listę dostępnych metod.
# Jako, że lista też jest obiektem,
# można na niej wywołać metodę dir. 

# Powyższe więc ma podobny skutek co dir(['inna', 'lista'])
>>> dir(['inna', 'lista'])
['__add__', ... , 'insert', 'pop', 'remove', 'reverse', 'sort']

# __class__ jest parametrem zawierającym typ klasy danego obiektu
>>> awesome_function.__class__
<type 'function'="">
>>> awesome_function.__setattr__
<method-wrapper '__setattr__'="" of="" function="" object="" at="" 0x1a02e60=""> # Interesujący fakt - jako, że nasza funkcja jest obiektem,
# możemy ją swobodnie przypisywać do zmiennej
>>> awesome_function.__name__
'awesome_function'
>>> variable = awesome_function
>>> variable.__name__
'awesome_function'
# Znów zwracamy uwagę na brak nawiasów
>>> variable
<function awesome_function="" at="" 0x1a02e60="">

Powyższe przykłady zawierają kolejną z zaskakujących różnic - możliwość introspekcji kodu, tj. badania parametrów danych obiektów wewnątrz aplikacji. Możemy na przykład w trakcie wykonywania programu zbadać, czy dana funkcja przyjmuje dany parametr, podejrzeć docstringa, czy też wylistować sobie (jak w powyższych przykładach) dostępne metody i w zależności od metod, jakie posiada dany obiekt, obrać tę lub inna drogę.

Odrobina podstaw

Aby rozumieć poniższe przykłady nie znając całkiem Pythona - niezbędne będzie wytłumaczenie chociaż odrobiny podstaw: list i słowników (dict). Słownik jest elementem bardzo analogicznym do PHPowej tablicy asocjacyjnej - posiada klucz oraz wartość. Przykładowy słownik definiuje się w ten sposób:

[PYTHON]

my_dict = {
    'fruit': 'apple',
    'car': '126P',
}

Lista natomiast jest elementem całkowicie nowym - zawiera tylko indeksację liczbową. Jest to zwyczajna lista elementów, sortowana w kolejności dodania. Przykładowa lista może wyglądać w ten sposób:

[PYTHON]

my_list = ["a", "b", "c"]

Warto wspomnieć, że iterując po liście oraz słowniku, zauważymy istotną różnicę - po elementach listy będziemy iterować się zawsze w ustalonej kolejności, natomiast po słowniku w losowej kolejności. Słownik w Pythonie jest elementem nieuporządkowanym w żaden konkretny sposób. Podczas tworzenia nowego słownika interpreter zmienia kolejność elementów.

Biorąc pod uwagę powyższe przykłady, do elementów można dostawać się w następujący sposób:

[PYTHON]

>>> my_list[2]
c

>>> my_dict['fruit']
apple

Dostępnych jest też bardzo wiele ciekawych funkcji i metod dla ww. typów, ale nie da się niestety wszystkiego pokazać - ciekawi Cię to? Przyjedź na warsztaty. :)

[PYTHON]

>>> my_dict.keys()
['fruit', 'cars']

Powiew świeżości

Jednym z ciekawszych momentów poznawania Pythona z punktu widzenia PHPowca jest zrozumienie, jak działa bardzo charakterystyczny dla Pythona element języka - tzw. list comprehension. Jest to możliwość bardzo prostej (i efektownie wyglądającej) iteracji po danym zbiorze oraz wykonywania działań na jego elementach. Najprościej będzie pokazać jak wygląda to w akcji:

[PYTHON]

# Najprostszy przykład, funkcja "range" generuje 
# listę liczb zgodnie z podanym parametrem. 
# Poniższy przykład zatem nie robi więc nic szczególnego.
>>> [x for x in range(10)]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# Lecz co jeżeli chcielibyśmy otrzymać co trzecią cyfrę?
>>> [x for x in range(10) if x % 3 == 0]
[0, 3, 6, 9]
# Albo użyć dodatkowych argumentów do range
>>> [x for x in range(0, 10, 3)]
[0, 3, 6, 9]

# Za pomocą list comprehension możemy iterować po wszystkim,
# co daje się iterować, a więc także stringi:
>>> php = 'python   has   power'
>>> [x.upper() for x in php if]
['P', 'Y', 'T', 'H', 'O', 'N', ' ', ' ', ' ', 'H', 'A', 'S', ' ', ' ', ' ', 'P', 'O', 'W', 'E', 'R']

# Użycie funkcji w trakcie iteracji wraz z warunkiem
>>> [x.upper() for x in php if x != ' ']
['P', 'Y', 'T', 'H', 'O', 'N', 'H', 'A', 'S', 'P', 'O', 'W', 'E', 'R']

Po przeanalizowaniu ostatniego przykładu prawdopodobnie zdajesz sobie sprawę z mocy, jaka drzemie w tym prostym zapisie. Na warsztatach obiecujemy pokazać Tobie o wiele bardziej zaawansowane zastosowania.

Ważną różnicą wynikającą z tego, iż wszystko w Pythonie jest obiektem jest sam styl w jakim będziesz wykonywał najbardziej podstawowe działania. W większości przypadków, choć nie zawsze, tam gdzie w PHP używałeś konkretnej funkcji globalnej, w pythonie będziesz używał metody danego obiektu. Pozwól, że zademonstuję:

[PHP]

<?php
$words = explode('-', 'Python-has-power');
echo implode(' ', $words);
?>
# wynik: Python has power

[PYTHON]

words = 'Python-has-power'.split('-')
print ' '.join(words)
# wynik: Python has power

Zauważ subtelną różnicę - w PHPowym przypadku wywołujemy funkcję ze stringiem jako parametr, w Pythonie natomiast wowołujemy metodę obiektu z jednym parametrem (sam ciąg znaków jest już właściwością klasy string).

Istotnym elementem Pythona, który w PHP występuje w nieco innej formie jest slice. Jest to składnia pozwalająca nam na bardzo eleganckie operowanie na podzbiorach list oraz stringów. Składnia slice wygląda następująco:

element[od:do:co_ile]

gdzie: “element” jest naszym obiektem po którym możemy iterować, "od" oznacza od którego elementu chcemy uzyskać nasz wycinek, "do" oznacza do którego elementu wyłącznie, a "co_ile" oznacza o ile elementów chcemy skakać w każdej kolejnej iteracji.
Najłatwiej pokazac to na przykładzie:

[PYTHON]

>>> li = ['a', 'b', 'c', 'd', 'e']
['a', 'b', 'c', 'd', 'e']

# Poniższy przykład oznacza:
# "od pierwszego do trzeciego elementu wyłącznie"
>>> li[0:3]
['a', 'b', 'c']

# Poniższy przykład oznacza
# "od pierwszego do czwartego elementu wyłącznie
# skacząc co 2 elementy"
>>> li[0:4:2]
['a', c']

Warto zauważyć, że każdy z tych parametrów może być ujemny co intuicyjnie oznaczać będzie "od końca", każdy z parametrów może by również pominięty, wtedy przyjmuje domyślną wartość.

[PYTHON]

# Lista odwrócona za pomocą slice
>>> li[::-1]
['e', 'd', 'c', 'b', 'a']

Odpowiednikiem slice w PHP jest funkcja array_slice która (jak sama nazwa wskazuje) działa na tablicach. W przypadku operowania na stringach konieczne jest użycie dodatkowych funkcji. W Pythonie slice dział identycznie zarówno na listach jak i stringach.

[PHP]

<?php
$string = 'Python-has-power';
$subarray = array_slice(str_split($string), -5, 5);
echo implode('', $subarray);
?>
# wynik: power

[PYTHON]

print 'Python-has-power'[-5:]
# wynik: power

Dict comprehension

Analogicznie do listy, comprehensions możemy używac również w przypadku słowników - zauważ jednak, że słownik jest elementem posiadającym zarówno klucz, jak i wartość - zatem obydwa te elementy muszą zostać wymienione z lewej strony, i rozdzielone - jak to w słowniku - dwukropkiem.

[PYTHON]

# Najprostszy przykład
>>> {a: "value" for a in range(10)}
{0: 'value', 1: 'value', 2: 'value', 3: 'value', 4: 'value',
5: 'value', 6: 'value', 7: 'value', 8: 'value', 9: 'value'}
# zmienne możemy stosować zarówno w kluczach, jak i wartościach
# poniższy przykład znaczy dosłownie:
# "utwórz słownik biorąc jako klucze liczby od 0 do 9,
# a jako wartości ich sześciany"
>>> {a: a**3 for a in range(10)}
{0: 0, 1: 1, 2: 8, 3: 27, 4: 64,
5: 125, 6: 216, 7: 343, 8: 512, 9: 729} 

W połączeniu z niesamowicie bogatą biblioteką Pythona list oraz dict comprehension niosą ze sobą nieograniczone możliwości. Korzystając na przykład z wbudowanego modułu string, możemy w banalny sposób uzyskać bardzo ciekawe efekty operując na alfabecie.

[PYTHON]

>>> import string
# poniższa zmienna zawiera najzwyklej w świecie litery od a do z
>>> string.ascii_lowercase
'abcdefghijklmnopqrstuvwxyz'
# utworzenie listy zawierającej alfabet
>>> [letter for letter in string.ascii_lowercase]
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l','m',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
# a może alfabet od tyłu, biorąc co 3 literę? 
>>> [letter for letter in string.ascii_lowercase][::-3]
['z', 'w', 't', 'q', 'n', 'k', 'h', 'e', 'b']
# Poniższy przykład korzysta z dict comprehension i oznacza:
# "utwórz słownik na podstawie liter alfabetu,
# jako klucze używając samych liter,
# a jako wartości kodowania dla danej litery"
>>> {letter: ord(letter) for letter in string.ascii_lowercase}
{'a': 97, 'c': 99, 'b': 98, 'e': 101, 'd': 100, 'g': 103,
'f': 102, 'i': 105, 'h': 104, 'k': 107, 'j': 106, 'm': 109,
'l': 108, 'o': 111, 'n': 110, 'q': 113, 'p': 112, 's': 115,
'r': 114, 'u': 117, 't': 116, 'w': 119, 'v': 118, 'y': 121,
'x': 120, 'z': 122}

Kwargs i rozpakowanie słownika

Kolejnym powiewem świeżości, którego doświadczysz przyjeżdżając do nas na warsztaty jest poznanie pythonowego rodziału na arguments oraz keyword arguments. Sam koncept jest prosty i na pewno już Tobie znany, są to argumenty funkcji podzielone na te przekazywane wraz z nazwą zmiennej oraz bez. Ciekawie robi się dopiero, gdy poznamy zastosowanie w praktyce.

Najpierw zdefiniujmy i wytłumaczmy funkcję:

[PYTHON]

>>> def repeat(times, word='php', separator=' ', capitalize=False):
...     output = word.upper() if capitalize else word
...     print separator.join(
...         [output] * times
...     )

>>> repeat(3)
'php php php'

Pierwsza linijka funkcji jest dosyć prosta: output = word.upper() if capitalize else word Jeżeli capitalize jest prawdą, zamieniamy wszystkie litery na wielkie.

Zapis [output] * times korzysta z tego, że lista w Pythonie obsługuje domyślnie przeładowanie operatora * i robi to, co jest najbardziej dla tego operatora intuicyjne - zwraca listę powieloną tyle razy, ile drugi parametr. Możemy to zapisać w następujący sposób:

[PYTHON]

>>> ['php'] * 3
['php', 'php', 'php']

Metoda join na stringu mówiąc w prosty sposób bierze wszystkie elementy z listy przekazanej przez parametr i "skleja" je danym stringiem, na którym jest wykonywana i zwraca zmontowany w ten sposób ciąg znaków.
Czyli ' '.join(['php', 'php', 'php']) produkuje nam ciąg znaków 'php php php'.
Rozumiejąc już co robi powyższy przykład, zwróćmy uwagę na argumenty z podaną domyślną wartością. Nie byłoby w nich nic wyróżniającego się, gdyby nie fakt, że możemy połączyć je z rozpakowywaniem słownika.

[PYTHON]

# Kolejność parametów nie ma znaczenia,
# zauważ pominięcie jednego z kwargs!
>>> repeat(3, separator='-')
'php-php-php'

# A teraz pora na magię pythonową, definiujemy słownik:
params = {
    'capitalize': True,
    'word': 'python',
}

# I "rozpakowujemy" go jako parametry funkcji!
>>> repeat(4, separator='|', **params)
'PYTHON|PYTHON|PYTHON|PYTHON'

Jest to kolejne zarazem proste i mające ogromne możliwości zastosowanie składni Pythona. Wyobraź sobie tylko budowanie słowników z parametrami wewnątrz funkcji i rozpakowywanie zwracanej przez nią wartości jako parametry kolejnej funkcji!

[PYTHON]

cool_function(
    "argument",
    some_kwarg='cool_param',
    **get_other_important_parameters()
)

Wiem, że powyższe może wyglądać na skomplikowane i zapewne nie od razu dostrzeżesz drzemiącą w tym moc. Gwarantuję jednak, że po przejściu naszego kursu będziesz doskonale rozumieć te zagadnienia i nauczysz się jak je zastosować w praktyce.

Python Has Power to nie jest akcja agitacyjna czy rekrutacyjna, choć daje możliwości rozpoczęcia współpracy z STX Next. Naszym celem jest przede wszystkim to, aby zainteresować Cię nowymi możliwościami, jakie otwiera zmiana języka programowania. Rynek Pythona nie jest łatwy, specjalistów jest mało, a jak wszyscy wiemy, natura nie znosi próżni. Wszyscy dobrzy specjaliści są zatem bardzo szybko wsysani przez firmy. A to oznacza, że w dobie dużych projektów webowych Python staje się świetną szansą i alternatywą zarazem. Daj więc sobie szansę na sukces i weź udział w naszych warsztatach - Python Has Power.

Piotr Mazurek Piotr Mazurek

I began my career in PHP technology when I graduated from University of Technology majoring in technical physics. Throughout my life I have always been interested in programming and from the young age I was involved in IT related projects. I also had an adventure with Linux servers administration. I am currently a Python developer at STX Next, where I am part of the team that creates software for customers in the banking sector. In addition, I write articles and I am involved in educational projects, supported by the company, such as Python Has Power.

comments powered by Disqus
Współpraca: programista