Artykuły | luty 11, 2025

Python i AI, Float i nie tylko – czyli jak produktywniej pracować z Pythonem? 

Jak zwiększyć efektywność pracy w Pythonie i zadbać o czysty kod? Jak asystenci AI wspierają codzienną pracę programistów? Przeczytaj artykuł i odkryj, jak programować efektywniej dzięki narzędziom AI i Machine Learning!

Python i AI

Podczas konferencji Python Summit 2024 zorganizowanej przez społeczność PyData i PyWaw w Warszawie w grudniu ubiegłego roku, miałem możliwość zapoznania się z prelekcjami na temat generatywnej sztucznej inteligencji, uczenia maszynowego, inżynierii danych, technologii webowych, cyberbezpieczeństwa, testowania, architektury systemów czy dobrych praktyk pisania czystego kodu. W pamięć zapadły mi szczególnie dwie prelekcje, które zainspirowały mnie do napisania tego artykułu. Pierwsza z nich dotyczyła produktywnej pracy z Pythonem (prelegent: Sebastian Buczyński, Software Architect / Consultant / Trainer z Bottega IT Minds), natomiast druga obejmowała temat liczb zmiennoprzecinkowych Float w Pythonie (Konrad Gawda, Cloud Evangelist z Orange Polska).

Zrozumienie poruszonych aspektów pozwala odpowiedzieć na istotne pytania. Jakie struktury wykorzystywać, by uniknąć krytycznych błędów? Jak zwiększyć efektywność pracy w Pythonie i zadbać o czysty kod? Jak asystenci AI wspierają codzienną pracę programistów?

Trochę historii na początek – przykłady problemów z liczbami zmiennoprzecinkowymi

Błąd systemu obrony przeciwrakietowej Patriot 

Podczas wojny w Zatoce Perskiej w 1991 roku system nie zadziałał prawidłowo i nie przechwycił rakiety Scud, która uderzyła w koszary wojskowe w Dhahran w Arabii Saudyjskiej, zabijając 28 osób. Przyczyną był błąd w obliczeniach czasu. System śledził czas za pomocą liczby całkowitej reprezentującej liczbę taktów zegara, przy czym każdy takt odpowiadał za około 0.1 sekundy. Aby obliczyć dokładny czas, liczba całkowita była mnożona przez 0.1, która w pamięci była reprezentowana jako liczba przybliżona ze względu na ograniczenia formatu IEEE 754. Z każdym kolejnym cyklem zegara błąd się kumulował, przez aż 100 godzin od uruchomienia systemu, ostatecznie narastając do około 0.34 sekundy. Ta różnica spowodowała, że system obliczył błędne położenie rakiety Scud i nie zadziałał na czas. Po incydencie wprowadzono aktualizację oprogramowania, która resetowała zegar systemowy częściej, zmniejszając wpływ kumulacji błędów. Ta tragiczna w skutkach historia amerykańskiego systemu obrony rakietowej Patriot to jeden z klasycznych przykładów błędu wynikającego z ograniczeń liczb zmiennoprzecinkowych. Ta historia pokazuje, że w tak istotnych obliczeniach należy unikać stosowania przybliżeń i używać precyzyjniejszych typów danych.  

Wybory parlamentarne w Schleswig-Holstein 

Innym przykładem są wybory parlamentarne z 1992 r. w niemieckim Schleswig-Holstein. W tym, jak i w większości niemieckich landów, obowiązuje system proporcjonalny, w którym partie muszą uzyskać co najmniej 5% głosów, aby otrzymać mandaty w parlamencie. Wyniki wyborów były prezentowane w procentach, a zaokrąglenia miały miejsce na różnych etapach obliczeń. Partia Zielonych uzyskała 4.97%, ale na etapie prezentacji wyników wartość została zaokrąglona do 5%, a więc próg został spełniony, co całkowicie zmieniło rozkład mandatów w parlamencie. Doprowadziło to do wielu kontrowersji i publicznej debaty, jednak mimo to wyniki zostały uznane za wiążące. W systemie nie przewidziano procedury na wypadek błędu zaokrąglenia.  

Float – liczby zmiennoprzecinkowe w Pythonie

W tym kontekście bardzo ciekawą prelekcją z mojej perspektywy była ta o typie Float, czyli liczbach zmiennoprzecinkowych w Pythonie.  

Liczby te można tworzyć na różne sposoby, poprzez dosłowne przekazanie liczby, czyli np. 1.0, używając klasy tj. float(1) lub poprzez działania matematyczne takie jak 1/1. 

Standard IEEE 754 binary64 

Z dokumentacji Pythona dowiadujemy się, że we wszystkich znanych implementacjach interpretera Pythona float zdefiniowany jest jako double. Bardziej szczegółowo typ ten jest opisany według standardu IEEE 754 binary64 i wygląda to następująco: 

Python i AI

Patrząc po kolei od lewej: 

  • 1 bit jest poświęcony na znak (liczba dodatnia lub ujemna). 
  • 11 bitów wyraża wykładnik potęgowy. 
  • 52 bity wyrażają ułamek. 

A wszystko to można przedstawić za pomocą poniższego wzoru: 

(-1)^sign x 2^(exp-1023) x 1.fraction 

Python i AI

Według omówionego standardu poniżej kilka przykładów reprezentacji binarnej: 

a) Liczba 0.0 

0 (sign) 00000000000 (exponent)

(0).0000000000000000000000000000000000000000000000000000 (fraction) 

b) Liczba 1.0 = 1 x 1.0 = 2^0 x 1.0 = 2^(1024-1024) x 1.0 

0 (sign) 01111111111 (exponent)

(1).0000000000000000000000000000000000000000000000000000 (fraction) 

c) Liczba 3.0 = 2 x 1.5 = 2^1 x 1.5 =2^(1025-1024) x 1.5 

0 (sign) 10000000000 (exponent)

(1).1000000000000000000000000000000000000000000000000000 (fraction) 

Precyzja typu Float 

Powyższe przykłady są dość proste z punktu widzenia reprezentacji binarnej, jednak problem pojawia się przy liczbach niecałkowitych, ponieważ nie zawsze jesteśmy w stanie zapisać je w pamięci z pełną precyzją. Można to zaobserwować, używając w Pythonie metody as_integer_ratio(), aby sprawdzić, jakie liczby są użyte do dzielenia, by uzyskać oczekiwany wynik. Jak widać na poniższym przykładzie, dla liczby 0.1 nie ma liczb 1 i 10, jak byśmy tego oczekiwali, tylko 3602879701896397 i 36028797018963968. Są to liczby, które dają najbardziej precyzyjne przybliżenie do 0.1 i możliwe do zapisu w pamięci według omawianego standardu. Natomiast to, że wywołując print() na obliczeniu 1/10 otrzymujemy 0.1, wynika wyłącznie z tego, że print() automatycznie zaokrągla nam do najkrótszego zapisu dziesiętnego, który się mieści w ramach ostatniego bitu precyzji.  

Python i AI

Inną przydatną metodą w Pythonie, pozwalającą zyskać więcej informacji na temat floatów, jest sys.float_info. Dzięki niej możemy się dowiedzieć, że float pozwala na przechowywanie do 15 cyfr znaczących (dodatkowo ewentualnie znak plus-minus i/lub przecinek).  

To wszystko może sprawić, że zaczniemy się zastanawiać nad tym, jak dużo ucieka nam precyzji podczas używania typu float. Do tego przydatna jest metoda math.ulp() (czyli Unit in the Last Place), co w praktyce zwraca nam różnicę pomiędzy podaną liczbą a następną, którą jesteśmy w stanie zapisać w tym formacie w pamięci. W przypadku małych liczb te różnice są bardzo marginalne, jednak przy takiej liczbie jak 2^52 ta różnica wynosi już równo 1.0. Oznacza to, że właściwie od tej liczby wzwyż całkowicie tracimy części ułamkowe. Idąc dalej, przy 2^53 różnica ta wynosi już 2.0. Zatem ważnym wnioskiem jest, by tak dużych liczb, nawet całkowitych, nie przechowywać jako float, ponieważ tylko liczby parzyste w tym przypadku zachowałyby swoją precyzję. 

Symbole specjalne 

W standardzie IEEE 754 przewidziano również miejsce dla specjalnych symboli. Jeśli 11-bitowa część exponent jest cała wypełniona jedynkami, a 52-bitowa część wyrażająca ułamek wypełniona zerami, to mamy do czynienia z reprezentacją binarną nieskończoności. Zapełniając zerem bit odpowiedzialny za znak otrzymujemy plus nieskończoność, a jedynką minus nieskończoność. 

Z kolei mając również 11-bitową część exponent wypełnioną jedynkami, ale także część wyrażającą ułamek wypełnioną jedynkami, otrzymujemy symbol NaN (Not a Number). Wszelkie operacje porównujące takie jak =, >, < z udziałem NaN zwracają False, co może być problematyczne w różnych okolicznościach. Zwłaszcza kiedy użytkownik ma braki w danych i sądzi, że porównywana jest faktyczna liczba, a nie pusta reprezentacja w postaci NaN.  

Zaokrąglenia 

Co jednak, jeśli nie jest potrzebna nam aż taka dokładność i chcemy uprościć jakieś obliczenia? Odpowiedzią są zaokrąglenia i należy wiedzieć, że uzyskać je można na różne sposoby. Co istotne, nie różnią się jedynie nazwy funkcji, ale ich implementacje mają odrębne podejścia. W prezentacji podczas Python Summit 2024 przedstawiono następujące metody: 

  1. Funkcja round() – zaokrągla zadaną liczbę do najbliższej liczby z określoną liczbą miejsc po przecinku, domyślnie do liczby całkowitej. Natomiast szczególnym przypadkiem jest sytuacja, gdy liczba jest równo w połowie i w obie strony jest taka sama odległość. Wtedy najmniej znacząca cyfra zaokrąglana jest do liczby parzystej, co według statystyków minimalizuje skumulowany błąd podczas wykonywania wielu operacji zaokrąglania. Czyli przykładowo zarówno 1.5, jak i 2.5 zostaną zaokrąglone do 2, jeśli interesuje nas liczba całkowita. Co ciekawe, w przypadku takich liczb jak 1.15 i 1.25 również obie powinny zostać zaokrąglone do 1.2, gdyby chcieć zachować dokładność do jednego miejsca po przecinku. Jednak tylko 1.25 zwróci poprawnie 1.2, a dla 1.15 zostanie zwrócone 1.1. Wynika to właśnie z tego, że liczby 1.15 nie da się precyzyjnie przechować w pamięci według standardu IEEE 754, a będzie to 1.1499999999999999. Czyli nie jest to de facto równo w połowie, a więc zaokrągli w dół do 1.1. 
  1. int() lub math.trunc() – co prawda nie są to funkcje zaokrąglające, tylko obcinające liczbę, ale ich efekt działa tak, jakbyśmy zaokrąglali liczbę w kierunku 0. Czyli przykładowo 1.9 zostanie ucięte (zaokrąglone) do 1, natomiast -1.9 zostanie sprowadzone do -1.  
  1. math.floor() lub x // 1 – pierwsza z nich po prostu zaokrągla w dół, w kierunku –nieskończoności. Natomiast druga to dzielenie bez reszty, przy czym dzieląc przez jeden, uzyskamy ten sam efekt co w przypadku pierwszej funkcji. Przykładowo dla obu z nich -1.9 zostanie zaokrąglone do -2, a 1.9 do 1. 
  1. math.ceil() – zaokrąglanie w górę, w kierunku + nieskończoności. Używając tego samego przykładu -1.9 zostanie zaokrąglone do -1, natomiast 1.9 zaokrągli do 2. 

Developer Experience – jak mierzyć efektywność developera?

W przeszłości na różne sposoby próbowano mierzyć efektywność developerów. Jedną z bardziej popularnych, a zarazem nietrafionych metod, było mierzenie efektywności poprzez liczbę napisanych linii kodu. Podczas drugiej z wspomnianych prezentacji przytoczono koncepcję, która nie tyle pozwala na mierzenie produktywności, co pomaga zadbać o efektywne środowisko pracy. Mowa tutaj o DevEX (Developer Experience), które wyróżnia 3 filary: 

  1. Stan przepływu (Flow state) – z punktu widzenia developera jest to stan głębokiego skupienia, w którym jesteśmy w stanie spokojnie pracować. Jeśli cele nie są jasno doprecyzowane, może wystąpić konieczność przerwania zadania, by ustalić nieścisłości. 
  2. Pętle zwrotne (Feedback loops) – oznacza, ile czasu mija, zanim developer się dowie, że kod nie działa. W przypadku testów jednostkowych ten feedback jest bardzo szybki, ale w przypadku code review na feedback trzeba zaczekać dłużej. 
  3. Ładunek kognitywny (Cognitive load) – oznacza wysiłek umysłowy wymagany do skutecznego pisania kodu. Jeśli programista spotyka się z projektem o innej strukturze niż zwykle albo nie ma do dyspozycji dokumentacji, do której zawsze miał dostęp, ten ładunek będzie wyższy. 

Narzędzia do efektywnej pracy w Pythonie

Wielu programistów rozpoczyna przygodę z Pythonem, gdyż zachęca ich intuicyjna składnia i wszechstronność zastosowań tego języka w analizie danych. W miarę rozwoju umiejętności w naturalny sposób poszukujemy narzędzi pozwalających zwiększyć efektywność pracy i ułatwiających życie. Podczas kolejnej prezentacji, w której miałem okazję brać udział w trakcie Python Summit 2024, zostały omówione takie narzędzia: 

  1. Formatery  

Formatowanie kodu to bardzo istotny aspekt, ponieważ to od tego głównie będzie zależało, czy programiście wygodnie się czyta oraz pisze kod. Jednak każdy z nas może mieć nieco inne preferencje, przyzwyczajenia, dlatego najlepiej, aby został przyjęty jeden ściśle określony sposób formatowania. Zaleca się korzystanie ze standardu PEP 8. Jest to dość długi dokument, dlatego dbanie samodzielnie o każdy szczegół mogłoby być bardzo czasochłonne – ładunek kognitywny rośnie. Dodatkowo podczas code review to byłaby rzecz, na którą trzeba zwracać uwagę, zatem przekłada się to na dodatkowy czas poświęcony po stronie zespołu, a i pętla zwrotna się wydłuża. I tutaj z pomocą przychodzą formatery kodu, które mogą wykonać całą pracę za nas. Przykładowe formatery kodu w Pythonie to: black, isort, ruff, yapf. 

  1. Lintery 

Są to narzędzia do statycznej analizy kodu, dzięki którym możemy dowiedzieć się o niektórych błędach jeszcze przed uruchomieniem kodu. Ponadto lintery są w stanie naprawiać za nas takie błędy jak np. literówki powstałe poprzez użycie nieistniejącej zmiennej. Inne kwestie, które mogą być sprawdzane automatycznie, to ilość znaków w każdej linii, długość nazw funkcji, zmiennych, ilość argumentów do funkcji itp. Przykłady linterów w Pythonie to: pylint, flake8, ruff. 
 

  1. Adnotacje typów i type checkery 

W kwestii typów zmiennych Python jest bardzo liberalny, ponieważ mamy tu do czynienia z typowaniem dynamicznym. Mimo to określanie typów, jakie przewidujemy dla danych zmiennych, jest postrzegane jako dobra praktyka w Pythonie. Jest to funkcja, która została wprowadzona od wersji Pythona 3.5. Należy mieć na uwadze, że adnotacje typów są jedynie komentarzem i nie są uwzględniane przez program, o ile nie ma zewnętrznej biblioteki, która by je interpretowała. Używając jednak PyCharma, jednego z najpopularniejszych IDE dla Pythona, możemy odnieść korzyści z adnotacji typów natychmiastowo. PyCharm posiada wbudowany type checker, dzięki czemu będziemy widzieć różne podpowiedzi dotyczące typów. Inne popularne zewnętrzne type checkery to: mypy, pyright czy też pyre. 

Rekomendacja dotycząca omówionych narzędzi bardzo przypadła mi do gustu. Według prelegentów Python Summit 2024 dobrym wyborem będzie ruff, ponieważ świetnie sprawdza się zarówno jako formater, jak i linter. Zastępuje inne narzędzia oraz jest bardzo szybki. Dodatkowym argumentem, by się bliżej przyjrzeć tej bibliotece, jest to, że w planach jest rozszerzenie jej funkcjonalności również o type checker. Jednak na ten moment należy korzystać ze sprawdzonych i popularnych narzędzi do sprawdzania typów, takich jak mypy oraz pyright.  

Asystenci AI jako wsparcie produktywnej pracy

Na koniec temat, bez którego trudno obecnie wyobrazić sobie przyszłość programowania – mianowicie narzędzia sztucznej inteligencji. Poza wspomnianymi tradycyjnymi narzędziami do usprawniania pracy w Pythonie podczas konferencji w prezentacjach osobną część poświęcono nowoczesnym podejściom do tworzenia oprogramowania wykorzystującym asystentów AI.  

O ile obecnie raczej nie ma co liczyć na to, że napiszą za nas cały program, o tyle mogą świetnie się sprawdzić jako usprawnienie pracy. Zainstalowanie takiej wtyczki jak chociażby Copilot może przyspieszyć proste operacje czy też tworzenie komentarzy dzięki trafnym podpowiedziom.  

Jeśli nie pamiętamy składni, argumentów biblioteki etc., sprawdzanie dokumentacji może okazać się już zbędne. Asystenci AI mogą również przyspieszyć pisanie prostych testów jednostkowych. Warto jednak najpierw napisać kilka z nich samodzielnie, by asystent mógł lepiej rozpoznać poprawny wzór. 

Poniżej kilka przykładów najbardziej popularnych narzędzi programistycznych.   

  • GitHub Copilot – narzędzie analizuje kontekst kodu i pozwala zautomatyzować, a tym samym przyspieszyć uzupełnianie fragmentów kodu. Przydatny w tworzeniu i przeglądzie dokumentacji oraz wyszukiwaniu konkretnych rozwiązań. 
  • Supermaven – sugeruje zależności i poprawki w kodzie, eliminując błędy i optymalizując wydajność aplikacji. Narzędzie może zasugerować użycie najlepszej biblioteki pod kątem danego projektu, co skraca czas analizy i przyspiesza proces wdrażania nowych rozwiązań. 
  • Cursor AI – usprawnia pracę programistów Python dzięki inteligentnemu autouzupełnianiu, generowaniu kodu na podstawie języka naturalnego oraz możliwości szybkiego refactoringu. 
  • Tabnine – pozwala uzupełniać kod w czasie rzeczywistym, co zwiększa produktywność i pomaga unikać błędów składniowych, a także eliminuje powtarzalność zadań związanych z pisaniem kodu. 
  • Sourcegraph Cody sprawdza się świetnie w zaawansowanym przeszukiwaniu kodu. Pozwala na zrozumienie zależności w projektach oraz – dzięki kontekstowej analizie całej bazy kodu – na szybkie odnajdywanie i poprawianie błędów. 
Code Faster Tlo 2

AI w programowaniu | bezpłatny e-book

Zautomatyzuj kodowanie z AI! Pobierz darmowy e-book i odkryj nowe możliwości

Pobierz teraz!

Podsumowanie

Mnogość dostępnych technologii w świecie IT potrafi przyprawić o zawrót głowy. Samo określenie roli w projekcie IT jeszcze nie determinuje wyboru narzędzi, z których będziemy korzystać. Decydować o tym będą w głównej mierze wymagania systemu. Czy powinien być nastawiony na wydajne przetwarzanie dużych ilości danych, czy może bardziej istotna jest prostota w implementacji i szybkie wdrożenie rozwiązania kosztem niższej wydajności? Odpowiedzenie sobie na te pytania czasem nie wystarczy, ponieważ bywa, że kilka technologii spełnia wszystkie wymagania i wybór sprowadza się do względów czysto estetycznych i preferencji osobistych.  

Na szczęście są społeczności i organizacje, które skupiają ludzi zainteresowanych określoną tematyką i pomagają odnaleźć się w gąszczu wiedzy oraz poszerzać horyzonty, by być na bieżąco. Dla osób związanych zawodowo z językiem programowania Python takim miejscem jest właśnie konferencja Python Summit. Prelekcje dostępne w trakcie Python Summit 2024 i zagadnienia, które omówiłem, stanowią tylko mały wycinek wiedzy, jaki można było wynieść z tego spotkania. Zapewniam, że każdy znalazłby coś dla siebie, nawet jeśli wybrane przeze mnie tematy nie są w centrum zainteresowania danego programisty. 

Przeczytaj także nasze artykuły dotyczące bibliotek Python:  

Od początku kariery zawodowej związany z danymi – od ETL, poprzez analizę, aż po wizualizację. Jego głównym językiem programowania jest Python. Gorący zwolennik czystego kodu. Nie boi się “ubrudzić sobie rąk” zagadnieniami algorytmicznymi i matematycznymi. Prywatnie miłośnik sportu, a w szczególności pływania.

Zapisz się do newslettera, ekskluzywna zawartość czeka

Bądź na bieżąco z najnowszymi artykułami i wydarzeniami IT

Informacje dotyczące przetwarzania danych osobowych

Zapisz się do newslettera, ekskluzywna zawartość czeka

Bądź na bieżąco z najnowszymi artykułami i wydarzeniami IT

Informacje dotyczące przetwarzania danych osobowych

Zapisz się do newslettera, aby pobrać plik

Bądź na bieżąco z najnowszymi artykułami i wydarzeniami IT

Informacje dotyczące przetwarzania danych osobowych

Dziękujemy za zapis na newsletter — został ostatni krok do aktywacji

Potwierdź poprawność adresu e-mail klikając link wiadomości, która została do Ciebie wysłana w tej chwili.

 

Jeśli w czasie do 5 minut w Twojej skrzynce odbiorczej nie będzie wiadomości to sprawdź również folder *spam*.

Twój adres e-mail znajduje się już na liście odbiorców newslettera

Wystąpił nieoczekiwany błąd

Spróbuj ponownie za chwilę.

    Get notified about new articles

    Be a part of something more than just newsletter

    I hereby agree that Inetum Polska Sp. z o.o. shall process my personal data (hereinafter ‘personal data’), such as: my full name, e-mail address, telephone number and Skype ID/name for commercial purposes.

    I hereby agree that Inetum Polska Sp. z o.o. shall process my personal data (hereinafter ‘personal data’), such as: my full name, e-mail address and telephone number for marketing purposes.

    Read more

    Just one click away!

    We've sent you an email containing a confirmation link. Please open your inbox and finalize your subscription there to receive your e-book copy.

    Note: If you don't see that email in your inbox shortly, check your spam folder.