Python - przegląd najpopularniejszych języków programowania #3

Opublikowane: 2023-01-26
Autor: Tomasz Przedziński

W naszym przeglądzie najpopularniejszych języków programowania omówiliśmy już języki C#, C i C++:

Przegląd kończymy na języku Python, którego popularność w ostatnich latach dosłownie eksplodowała, zapewniając mu pierwsze miejsce w indeksie.

Wykres - Index for Python

Popularność języka programowania Python w czasie. Źródło: www.tiobe.com

Jest wiele czynników, które za tym stoją. Najistotniejsze obszary, gdzie obserwujemy największy wzrost zainteresowania tym językiem to:

Zastosowanie języka Python w tych obszarach ma bardzo dużo sensu, z czym mam nadzieję zgodzicie się po lekturze tego artykułu.

Prostota, wygoda i pełna swoboda

Python różni się od omawianych do tej pory języków praktycznie w każdym względzie. Już po chwili z nim spędzonej można uznać, że każdy inny język programowania jest trudny w użyciu, wymaga lat doświadczenia i jest najeżony problemami, których w Pythonie po prostu nie ma.

Python jest jednym z najprostszych języków do nauki. Jego zastosowanie jest uniwersalne – od prostych skryptów, po złożone środowiska do uczenia maszynowego. Dzięki ogromowi bibliotek, z których można skorzystać, wpisując jedną linijkę w konsoli (lub jednym kliknięciem w IDE), programista już od pierwszych minut pracy z językiem Python ma na wyciągnięcie ręki potężny arsenał gotowych rozwiązań. W ciągu paru minut można napisać szereg programów, wliczając w to serwer http czy prostą grę z interfejsem graficznym. Dla przykładu, po zaimportowaniu odpowiedniego modułu, wyświetlenie okienka pop-up z napisem „Hello world!” zajmuje zaledwie dwie linijki kodu.

Przykładowy kod w języku Python

Program “Hello world!” w Pythonie

Przykładowy kod w języku Python

Program wyświetlający pop-up z napisem “Hello world!”. Działa na Windows, Linux i podejrzewam, że na wielu innych systemach. Wymaga jednorazowo wpisania dodatkowej linijki w konsoli: pip install PyMsgBox

Python jest językiem interpretowanym, co oznacza, że nie wymaga kompilacji i czas od momentu dodania linijek kodu do momentu uruchomienia nowej wersji jest bardzo krótki. W dodatku podstawowa gramatyka tego języka jest tak prosta, że można się jej nauczyć i efektywnie używać w parę godzin. Jednocześnie wspiera wiele odmiennych paradygmatów programowania, w tym programowanie obiektowe, co oznacza, że można pracować w stylu, który najlepiej pasuje do danego zadania.

W Pythonie podział kodu na moduły następuje w sposób naturalny i prosty, co z pewnością przyczynia się do powstawania ogromnej bazy gotowców. Rozwiązując nowy problem, zawsze warto sprawdzić, czy ktoś już tego nie zrobił, bo może wystarczy jedna linijka kodu, by zaimportować i użyć działającego już rozwiązania.

Python zarządza pamięcią przy użyciu mechanizmów odśmiecania pamięci (ang. Garbage Collection), podobnie jak jest to zrobione w platformie .NET, co znacząco odciąża umysł programisty. Posiada również narzędzia do programowania asynchronicznego1, wliczając mechanizmy realizujące zarówno podejście TAP (ang. Task-based Asynchronous Pattern) jak i EAP (ang. Event-based Asynchronous Pattern). Najbardziej jednak spośród wszystkich innych języków wspomnianych w tym artykule wyróżnia Pythona jedna cecha – dynamiczne typowanie.

Dynamiczne typowanie, w odróżnieniu do statycznego obecnego w większości współczesnych języków programowania, oznacza, że typ zmiennych nie musi być znany w momencie pisania programu. Jest ustalany w trakcie jego uruchomienia, co ma bardzo istotny wpływ na sposób pisania kodu. Możemy, na przykład, napisać funkcję, która oczekuje określonego typu zmiennych, ale tego typu nie wymusza. Rozszerzając później tę funkcję na inne typy, nie musimy wprowadzać zmian ani w API ani w tych częściach kodu, które są dla tych typów wspólne. Może się nawet okazać, że nie będziemy musieli nic zmieniać, ponieważ dzięki typowemu dla Pythona stylowi programowania zwanemu duck typing2, możemy zastosować podejście odwrotne – rozszerzyć funkcjonalność innych typów tak, by spełniały oczekiwania naszej funkcji3.

A to tylko początek. Możemy, na przykład, oczekiwać, że nasz typ ma funkcję „test” i obsłużyć sytuację, w której dany typ tej funkcji nie ma. Nie ograniczamy w ten sposób listy typów, dla których nasza funkcjonalność działa poprawnie, jedynie obsługujemy wyjątki. By osiągnąć ten sam efekt w innych językach programowania, należy stworzyć odpowiednie abstrakcje i algorytmy na nich operujące. Innymi słowy, nierzadko trzeba przerobić spory kawałek kodu, by z obsługi jednego typu zrobić obsługę dwóch. Możliwe, że podobną pracę trzeba będzie zrobić by obsłużyć trzeci. W języku Python takie rozszerzanie kodu jest trywialne.

Gdyby tego było mało, w wielu aspektach Python traktuje zmienne i kolekcje zmiennych (tablice, tuple, itp.) tak samo. Do naszej funkcji, zamiast jednej zmiennej, możemy podać cały ich szereg. Te same zasady dotyczą tego, co zwracamy z funkcji. I znów – większość języków programowania bardzo rygorystycznie podchodzi do tego, co dana funkcja może zwrócić. W Pythonie ten problem nie istnieje. Możemy zwracać co chcemy i kiedy chcemy, nie deklarując z góry co dana funkcja będzie zwracać. Zwracany typ może też wynikać z argumentów funkcji i być inny dla każdego jej wywołania.

Jest wiele rzeczy, którymi nie trzeba się przejmować, programując w Pythonie. Wystarczy usiąść i zacząć pisać kod. Potrzeba obsługi tego i tamtego? Pewnie jest na to moduł. Potrzeba ogólnej funkcji? Nie trzeba szeregu klas, abstrakcji, definicji typów – piszemy tylko to, czego potrzebujemy. A gdy projekt robi się bardziej złożony, możemy opakować co trzeba w klasy lub moduły i zorganizować kod lepiej. Importowanie własnych modułów to, znów, tylko jedna linijka. Nie myślimy o kompilacji, nagłówkach i innych „przeżytkach” statycznie typowanych języków. Nie bez powodu początkujący programiści najchętniej zaczynają pracę właśnie z Pythonem. Dzięki niemu bardzo łatwo nauczyć się tego, co w programowaniu najważniejsze – umiejętności rozwiązywania problemów. Do tego niepotrzebny jest sztywny, ograniczający pracę język zmuszający do nauczenia się i używania masy zbędnych mechanizmów, zanim napisze się pierwszy porządny program. Szybkość, z jaką można rozpocząć pracę z językiem Python, to zdecydowanie najsilniejszy magnes jakim przyciąga on początkujących programistów, przysparzając sobie tym samym popularności.

Ciężko wyobrazić sobie jak bardzo różni się programowanie w językach z dynamicznym i statycznym typowaniem, nie mając choć odrobiny doświadczenia w obu. Jeśli nie macie takiego doświadczenia, to uwierzcie mi, gdy piszę: różni się diametralnie. Jednak dynamiczne typowanie to nie tylko same zalety. Jak mała gwiazdka w reklamie czy przy umowie – wady tego rozwiązania zdają się ukrywać przed programistą do czasu, gdy głębiej wczyta się w jej treść.

Python – kochać czy nienawidzić?

Python to najbardziej wewnętrznie spolaryzowany język jaki znam. Mam na myśli to, że każdą jego kluczową cechę mógłbym pochwalić, a później dodać „ale” i dołożyć zarzut.

Łatwo rozpocząć w nim pracę, ale rozszerzona modułami funkcjonalność bardzo szybko staje się bardzo skomplikowana. Szybko pisze się w nim proste projekty, ale długoterminowo trudniej utrzymać te bardziej złożone. Cieszy brak kompilacji i wiele innych aspektów pracy z interpreterem kodu, ale jest to obarczone znacznym kosztem wydajności programu4. Dynamiczne typowanie znacząco ułatwia szybkie pisanie nowego kodu, ale kosztem analizy statycznej, która w statycznie typowanych językach eliminuje szereg podstawowych błędów programowania.

Przykładowy kod w języku Python

Python pozwala podać cokolwiek jako argument funkcji. Przykład po lewej pozwala wypisać dowolne rzeczy na ekran. Jednak jego zachowanie rzadko jest zgodne z oczekiwaniem. Aby poprawnie obsłużyć listy i kolekcje musimy rozszerzyć kod na przykład tak, jak po prawej stronie. Dodatkowo wypadałoby obsłużyć warunki brzegowe, jak obsługę braku argumentu albo obsługę funkcji. Nie mamy możliwości ograniczenia typów, dla jakich użytkownik użyje naszą funkcję, możemy jedynie „sugerować” użycie przy pomocy podpowiedzi. Powinniśmy więc zabezpieczyć się przed jej błędnym użyciem. Brak zabezpieczeń może powodować błąd lub, co gorsze, kod zadziała niezgodnie z oczekiwaniem, nie raportując błędu.

Ten ostatni punkt jest w moim doświadczeniu najbardziej frustrującym ze wszystkich5. Brak kompilatora w Pythonie oznacza, że tak proste błędy, jak choćby literówki w nazwach zmiennych, wychodzą dopiero w momencie, gdy interpreter dojdzie do błędnej linii kodu. Nietrudno wyobrazić sobie, jak istotne może spowodować to problemy. Spójrzmy na ten przykład: jeśli napisaliśmy kawałek kodu, uwzględniając obsługę błędów, kod odpowiedzialny za tę czynność zostanie wykonany (co również oznacza: sprawdzony) tylko, gdy wystąpi błąd. Jeśli błąd nigdy nie wystąpi, nie będziemy wiedzieli, czy popełniliśmy literówkę czy nie – to samo dotyczy każdego innego rozgałęzienia w kodzie. Błędy w kodzie odpowiedzialnym za obsługę błędów są o tyle istotne, że zwykle testy go nie pokrywają. Przynajmniej nie od samego początku.

Przykładowy kod w języku Python

Powyższy kod prezentuje typowy przykład problemów z Pythonem. W przykładzie po lewej literówka w linijce 4 nie zostanie podświetlona przez IDE bo ‘something’ może mieć funkcję ‘appen’. Nie ma mechanizmu, który zawczasu mógłby stwierdzić, że taką funkcję posiada lub nie. Po uruchomieniu, ten kod wyrzuci błąd dla obu przypadków użycia nawet, jeśli w zamierzeniu, dla drugiego powinien wypisać ‘10’. Z kolei przykład po prawej stronie ukazuje gorszy, moim zdaniem, problem: błędna suma typów ‘str’ i ‘int’ w linijce 9 nie zostanie wyłapana, ponieważ ta linijka nigdy nie zostanie wykonana – oba wywołania funkcji nie powodują wyjątku. Nie wiadomo, kiedy ten błąd zostanie odkryty. W tym przypadku, jego konsekwencje są niewielkie, ale nawet taki zdawałoby się „niewinny” błąd na produkcji potrafi być problematyczny, ponieważ potrafi zakończyć działanie całego programu.

Ten problem pociąga za sobą szereg konsekwencji. Jeśli tworzymy kod produkcyjny lub moduł dostępny publicznie, powinniśmy przetestować każdą jego ścieżkę. Nie dlatego, że manager jako cel projektu ustawił 100% pokrycia kodu testami, a dlatego, że nie przechodząc przez każdą linijkę kodu w testach, nie będziemy mieć pewności, że nie ma w niej podstawowych błędów. Wprawdzie parser, który analizuje wykonywany plik, sprawdzi brakujące klamry, cudzysłowy i podobne problemy w strukturze kodu, ale nie ma możliwości stwierdzenia, czy w danym wywołaniu funkcji jest literówka. Nawet najdroższe płatne IDE nie są w stanie przeprowadzić pełnej analizy kodu, bo dana funkcja może zostać wywołana z innym, zupełnie nieoczekiwanym typem. Nawet testy pokrywające 100% kodu nie sprawdzą go dla każdego wspieranego typu.

W praktyce to oznacza znacznie odłożoną w czasie detekcję błędów. Z jednej strony jest to problem, którym powinniśmy się przejmować, a z drugiej – te błędy muszą występować w rzadziej używanych ścieżkach programu, inaczej już dawno byśmy je znaleźli. Problem dotyczy zatem pewnego rodzaju kompletności czy świadczenia jakości kodu – trudno, a przynajmniej trudniej niż w innych językach programowania, zagwarantować, że kod Pythona zadziała poprawnie, uwzględniając przyszłe zastosowania i rozwój projektu.

Do tego dochodzi jeszcze problem wynikający z samej obsługi błędów wykonywanej przez interpreter. W przypadku, gdy interpreter Pythona znajdzie problem, rzuca wyjątkiem. To znów, wspaniała cecha interpretera, do której muszę dodać ALE – ta cecha to zmora początkujących programistów. Jeśli ktoś nieopatrznie obejmie fragment kodu typową dla początkujących formą try-except, która łapie wszystkie możliwe wyjątki, może złapać również wyjątek interpretera i obsłużyć go w sposób generyczny, tłumiąc faktyczną przyczynę jego rzucenia. To jeszcze bardziej oddala w czasie detekcję błędów.

Obsługa błędów jest o tyle istotna, że pisząc kod, zwykle nie zakładamy, że błąd wystąpi. Zazwyczaj też nie testujemy takiego scenariusza, a przynajmniej nie od razu. Dynamiczne typowanie oraz interpretacja kodu znacząco utrudniają nie tylko zabezpieczenie się przed błędami, ale i rozpoznanie ich przyczyn, gdy już wystąpią.

Oczywiście podobne problemy dotykają też innych języków programowania, ale nie na taką skalę. Przyczyną jest kolejna odmienność w stylu pracy z językiem Python – w większości projektów pisanych w statycznie typowanych językach stosuje się praktykę LBYL (ang. Look Before You Leap), która w skrócie stara się ograniczać obsługę wyjątków do sytuacji faktycznie wyjątkowych i nakazuje weryfikować wymagania początkowe różnych funkcji i modułów. Python wyznaje doktrynę EAFP (ang. Easier to Ask for Forgiveness than Permission), która mówi dokładnie odwrotnie – nie weryfikuj, tylko próbuj zrobić to, co chcesz. Najwyżej spowodujesz wyjątek i taką sytuację obsłużysz. Takiego samego podejścia należy się również spodziewać po zewnętrznych modułach używanych w projekcie, co w konsekwencji oznacza, że dużo częściej zachodzi konieczność obsługi różnego typu wyjątków.

Zwiększona liczba wyjątków pociąga za sobą chęć pójścia na łatwiznę w takim czy innym miejscu w kodzie i to w konsekwencji prowadzi do generycznych try-except, które czasem obejmują zbyt szeroki zakres rzucanych wyjątków, przez co ukrywają różne istotne błędy w kodzie.

To jak jest z tym Pythonem? Fajny czy niefajny?

Po lekturze poprzedniego rozdziału można wnioskować, że praca z Pythonem to same problemy. Warto zatem podkreślić, że autor tego artykułu na co dzień pracuje z językami statycznie typowanymi, więc różnice w podejściu i powstałe z tego powodu błędy są dla niego wyraźniejsze, niż dla kogoś, kto całe życie programuje w Pythonie.

Nie można w żaden sposób stwierdzić, że dynamicznie typowany Python jest lepszy lub gorszy od statycznie typowanych języków – jest po prostu inny. Ma inne zalety, inny zestaw problemów i, w konsekwencji, inne zastosowanie. Nie znajdziesz w języku Python wymagających wysokiej wydajności gier6, ani też złożonych algorytmów numerycznych czy uczenia maszynowego. Wszystko, co wymaga dużej złożoności obliczeniowej, pisane jest w przeznaczonych do tego językach, a mimo to możesz w Pythonie napisać grę pod silnik Source, wykonywać skomplikowane i pracochłonne obliczenia macierzowe z użyciem biblioteki NumPy, czy uczyć sieci neuronowe z użyciem środowiska Tensorflow lub PyTorch. Jeśli projekt jest popularny, to istnieje szansa, że jakaś społeczność programistów Pythona stworzy do niego odpowiedni interfejs, a jeśli dane narzędzie pozwala użytkownikowi dodać własny kod, to możliwe, że użytkownik może go napisać właśnie w języku Python.

Obszary naukowe i naukowo-biznesoweBack-endInne
  • uczenie maszynowe (machine learning)
  • Big Data
  • Data Science
  • NLP
  • aplikacje i serwery webowe
  • interfejsy graficzne
  • skrypty do innych narzędzi (graficznych, projektowych, silniki gier, itp.)

Python, będąc językiem uniwersalnym, ma bardzo szerokie spektrum zastosowań. Najchętniej jednak jest wykorzystywany tam, gdzie programowanie jest tylko narzędziem do osiągnięcia dużo bardziej złożonego celu, jak np. w środowiskach naukowych.

Co łączy języki C#, C, C++ i Python?

Może zabrzmi to niewiarygodnie, ale akurat te cztery języki: Python, C, C++ i C# dobrze ze sobą współgrają. Zdarzyło mi się dwukrotnie (w dwóch różnych firmach) spotkać projekt, w którym wszystkie cztery języki były wykorzystywane równocześnie – oczywiście z różnym przeznaczeniem. Oba projekty były projektami z oprogramowaniem wbudowanym, gdzie warstwa sterowników urządzenia była napisana w C, warstwa aplikacji w C++11, w C# powstała aplikacja desktopowa do komunikacji z urządzeniem (zarządzania, konfiguracji, debugowania, aktualizacji, etc.), a Python użyty był do testów funkcjonalnych7.

Wiele projektów stara się w pełni wykorzystać różnorodność, jaką oferują różne języki programowania. W mojej ocenie, do każdego z zadań we wspomnianych projektach wybrany został najodpowiedniejszy język, uwzględniający możliwości i doświadczenie zespołu.

W dzisiejszych czasach ciężko znaleźć projekt napisany w jednym języku i bardzo wąska specjalizacja zaczyna być dla programistów ograniczająca. Warto zatem poszerzać horyzonty i szukać innych języków, których warto się nauczyć. Index TIOBE, który posłużył nam za przewodnik w tej serii artykułów, jest dobrym odzwierciedleniem rynku pracy, ale do wyboru ewentualnego przyszłego kierunku rozwoju bardziej polecamy ankiety wydawane co roku przez StackOverflow8. Zawierają one dużo detali i uwypuklają obecne trendy wśród programistów tej bardzo szerokiej i zróżnicowanej społeczności. Ukazują m.in. nieco odmienne proporcje, jeśli chodzi o cztery omawiane w tym cyklu języki. C i C++ są raczej marginalizowane, a Python i platforma .NET zyskują coraz więcej fanów. Bez wątpienia jednak Python zajmuje obecnie znaczącą pozycję na rynku języków programowania i ta sytuacja nieprędko się zmieni.

Masz pomysł na aplikację mobilną, webową lub własny system i szukasz doświadczonego partnera technologicznego?  Obejrzyj projekty, które zrealizowaliśmy z klientami, którzy potrzebowali  oprogramowania na zamówienie. Jeśli masz gotowy plan działania i opis aplikacji, będziemy w stanie przygotować wycenę nawet w 24 godziny od przesłania specyfikacji. Jeśli nie – nic nie szkodzi. Skontaktuj się z nami i umów na bezpłatną konsultację, aby wspólnie opracować dalsze kroki.

Wszystko, co powinieneś wiedzieć o tworzeniu aplikacji

1 – Python wspiera zarówno programowanie wielowątkowe jak i wieloprocesowe. Oba podejścia można wykorzystać w innych językach programowania, ale Python pozwala łatwo wybierać między jednym a drugim.
2 – To określenie nie ma odpowiednika w języku polskim. Wywodzi się z powiedzenia jeśli chodzi jak kaczka, kwacze jak kaczka, to musi być kaczką i symbolizuje podejście, w którym typ analizowanego obiektu wynika z cech, jakie ten obiekt reprezentuje, a nie ze sztywnej deklaracji, że dany obiekt jest kaczką. W uproszczeniu takie podejście oznacza, że poprawnym jest traktowanie ludzi jako kaczki, jeśli jedyna cecha, na którą zwracamy uwagę, to obecność dwóch nóg.
3 – Żeby było ciekawiej w Pythonie możemy to zrobić nawet w obrębie wywoływanej funkcji! Dla przykładu: jeśli potrzebujemy, by typ, który wchodzi do funkcji, „kwakał”, ale otrzymujemy typ, który nie kwacze, możemy dodać mu domyślne kwakanie tylko po to, by zrealizować potrzebną funkcjonalność. Brzmi to dość skomplikowanie i nieintuicyjnie, i dla wielu programistów przyzwyczajonych do innych języków programowania takie właśnie jest, ale tego typu mechanizmy to prawdziwa potęga Pythona.
4 – Niesławnie, Python wypada bardzo blado w porównaniach takich jak to: https://thenewstack.io/which-programming-languages-use-the-least-electricity/ gdzie analizowane jest zużycie energii, czasu i pamięci na rozwiązanie trudnych problemów numerycznych. Z 27 analizowanych języków Python zajmuje przedostatnie miejsce w zużyciu energii i w czasie wykonania (porównanie jest robione względem języka C). Jednak tak dramatyczny wynik może być traktowany jako krzywdzący – języka Python nie używa się do intensywnych obliczeń numerycznych, właśnie ze względu na to, że z oczywistych powodów będzie w tym zastosowaniu znacznie wolniejszy. Z tego też powodu biblioteki numeryczne dla języka Python są pisanie w C lub C++, a jedynie interfejs dostarczają w języku Python. To typowe podejście w wielu współczesnych środowiskach obliczeniowych, w których można pracować w języku Python.
5 – Nie jest bez znaczenia fakt, że pracuję głównie ze statycznie typowanymi językami. Przełączając się na pracę z Pythonem, często zapominam, że trzeba zmienić nawyki.
6 – Nie licząc raczej niszowych modułów skierowanych do hobbystów.
7 – Dodatkowo w jednym z tych projektów Python był użyty do napisania serwerów zarządzających i testujących urządzenia (wliczając generację prostego interfejsu http dla testerów), a w drugim C# używany był do analizy danych telemetrycznych zebranych z działających urządzeń.
8 – Ankieta na rok 2022 dostępna jest tutaj: https://survey.stackoverflow.co/2022/
Autor: Tomasz Przedziński,
Architekt Oprogramowania

Rocznik 86, skończył studia magisterskie z Informatyki Stosowanej na Uniwersytecie Jagiellońskim w Krakowie i obronił doktorat z inżynierii oprogramowania na AGH. Specjalizuje się w C++, ale mocno polubił się ze środowiskami embedded. Nie ukrywa sympatii do algorytmiki i optymalizacji rozwiązań. W pracy zawodowej stawia na jakość, budując zaawansowane środowiska testowe lub rozbudowując istniejące.

SKONTAKTUJ SIĘ
Wypełnij
formularz.
Skontaktujemy się z Tobą,
żeby umówić rozmowę
w dogodnym dla Ciebie terminie.