Czego nie powinniśmy robić w Pythonie? #5

Cześć!

Na jakiś czas odsunąłem na bok tę serię. Muszę przyznać, że trochę mnie zmęczyło skupianie się nad tym, czego nie powinno się robić. Efektem tej przerwy było powstanie innych, równie wartościowych wpisów. Niemniej jednak czułem, że nie zamknąłem tego tematu należycie, więc dziś zapraszam na ostatni wpis z serii „Czego nie powinniśmy robić w Pythonie?„.

Nim przejdziemy do sedna, podrzucam linki do reszty wpisów:

Unikanie typowania

Nowsze wersje Pythona pozwalają na definiowanie typów. Praktycznie już od 3.6 jest to w pełni działający mechanizm, szczegółowo opisany w dokumentacji.

Kod bez typowania oczywiście zadziała, więc pytanie po co definiować typy? Typowanie wprowadza porządek i pozwala pisać spójniejszy kod. Mimo że nie działa ono tak jak w Javie (bo zdefiniowanie typu np. w argumencie metody, nadal pozwala nam przekazać coś niezgodnego), to jest nieocenioną pomocą dla PyCharma. A dzięki temu PyCharm pomaga nam.

Zerknij na kod:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from abc import ABC, abstractmethod


class Animal(ABC):
  @abstractmethod
  def _voice(self) -> str: pass

  def speak(self) -> None:
      print(self._voice())


class Dog(Animal):
  def _voice(self) -> str:
      return "Hau hau"


class Cat(Animal):
  def _voice(self) -> str:
      return "Miauł miauł"


selected_animal: str = input("Type dog or cat to get voice!")

globals_objs: dict = globals()
animal = globals_objs[selected_animal.title()]()

animal.speak()

Zauważ, że w tym kodzie typ zmiennej animal jest określany w runtime’ie. Patrząc na ten kod wiemy, że będzie tam obiekt klasy Animal. Jednak PyCharm tego nie może wiedzieć, więc nie dostaniemy na zmiennej animal podpowiedzi do możliwych metod tego obiektu.

Dzięki typowaniu możemy określić mu, jaki to jest typ i tym samym uzyskać podpowiedź:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from abc import ABC, abstractmethod


class Animal(ABC):
  @abstractmethod
  def _voice(self) -> str: pass

  def speak(self) -> None:
      print(self._voice())


class Dog(Animal):
  def _voice(self) -> str:
      return "Hau hau"


class Cat(Animal):
  def _voice(self) -> str:
      return "Miauł miauł"


selected_animal: str = input("Type dog or cat to get voice!")

globals_objs: dict = globals()
animal: Animal = globals_objs[selected_animal.title()]()

animal.speak()

Sklejanie nazw metody

Nie jedna osoba pewnie widziała kod podobny do tego:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class A:
  def get_a(self) -> str:
      return "a!"

  def get_b(self) -> str:
      return "b!"

  def get_c(self) -> str:
      return "c!"


value: str = "a"
selected_method: str = f'get_{value}'

a: A = A()

print(getattr(a, selected_method)())

Błagam, nie róbcie tak! Taki kod tragicznie się czyta. Siedzisz i kopiesz debuggerem, żeby dowiedzieć się, co on tam tak pokrętnie wykonuje! Wiem, że takie rozwiązanie kusi, ale tylko do czasu, kiedy jest potrzeba analizy takiego kodu. Wtedy przestaje być to wygodne.

Oczywiście przykład nie pokazuje problemu dostatecznie, ale ubierzcie go w wyobraźni w dodatkowe abstrakcje, dodajcie kilkanaście tysięcy plików obok, skomplikowany przypadek, który badacie, a wyjdą Wam włosy z głowy. Same.

Podobny typ problemu pojawił się również w poprzednim akapicie. Z tą różnicą, że tam jest tworzenie obiektu na podstawie klasy, która jest tworzona ze stringa, którego przekazuje klient. Tak się zdecydowanie nie robi! Tam po pierwsze powinna być walidacja, czy umiemy obsłużyć to, co przyszło do nas od klienta, po drugie jakaś metoda wytwórcza, która tworzy obiekt. I wolę w niej po prostu IFy…

I też wiem, że czasem ciężko inaczej… Ale chociaż starajmy się unikać takich rozwiązań. A jak nie możemy, to dopisujmy logi, typy, wszystko, co pomaga ten kod zrozumieć.


Czekaj, stop!

Podoba Ci się to co tworzę? Jeśli tak to zapraszam Cię do zapisania się na newsletter:

Aby potwierdzić swoją subskrypcję, odbierz pocztę i kliknij w link potwierdzający:) jeśli maila nie ma to poczekaj chwile i/lub sprawdź folder spam/inne/oferty itp :)

Aby potwierdzić swoją subskrypcję, odbierz pocztę i kliknij w link potwierdzający:) jeśli maila nie ma to poczekaj chwile i/lub sprawdź folder spam/inne/oferty itp :)
a w ramach prezentu otrzymasz całkowicie za darmo, dwa dokumenty PDF „6 (nie zawsze oczywistych) błędów popełnianych podczas nauki programowania” który jest jednym z efektów ponad siedmioletniej pracy i obserwacji rozwoju niejednego programisty oraz „Wstęp do testowania w Pythonie”, będący wprowadzeniem do biblioteki PyTest.
Jeśli to Cię interesuje to zapraszam również na swoje social media.

Jak i do ewentualnego postawienia mi kawy :)

Ciężkie obliczenia na wątkach

Python, a dokładnie CPython, ma pewien mankament, zwany GIL. GIL to Global Interpreter Lock, czyli po prostu mutex, który nie pozwala wielu wątkom używać interpretera w tym samym czasie. W efektcie obliczenia średnio dają się zrównoleglić. Zerknij na kod:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import time


def count(_from, _to):
  while _from >= _to:
      _from -= 1


start = time.time()

count(40000000, 0)

end = time.time()
print('Time taken in seconds -', end - start)

Ten kod wykonuje się w okolicach 2 sekund. Odlicza on od 40000000 do zera. Czy jeśli zrobimy dwa wątki, jeden od 40000000 do 20000000, a drugi od 20000000 do 0, to kod będzie trwał w okolicach sekundy? Sprawdźmy!

Uruchamiam taki kod:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from threading import Thread
import time


def count(_from, _to):
  while _from >= _to:
      _from -= 1


start = time.time()

t1 = Thread(target=count, args=(40000000, 20000000))
t2 = Thread(target=count, args=(20000000, 0))

t1.start()
t2.start()

t1.join()
t2.join()

end = time.time()
print('Time taken in seconds -', end - start)

A jego efektem jest wynik… Lekko większy od 2 sekund.

Co pokazuje że zrównoleglenia brak.

Alternatywnym rozwiązaniem jest użycie procesów zamiast wątków, czyli skorzystać z multiprocessing, np:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from multiprocessing import Process

import time


def count(_from, _to):
  while _from >= _to:
      _from -= 1


start = time.time()

p1 = Process(target=count, args=(40000000, 20000000))
p2 = Process(target=count, args=(20000000, 0))

p1.start()
p2.start()

p1.join()
p2.join()

end = time.time()
print('Time taken in seconds -', end - start)

Co daje wynik w okolicach sekundy. Oczywiście procesy są cięższe od wątków, więc trzeba się liczyć z konsekwencjami tego rozwiązania.

Zmierzając do brzegu

W ramach tej serii artykułów powstało 5 wpisów, każdy średnio po trzy akapity, czyli łączenie około 15 rad na temat tego, czego warto unikać, pisząc programy w Pythonie. To sporo fajnej wiedzy. Mam nadzieje, że dowiedziałeś się czegoś przydatnego.

Dzięki za wizytę,
Mateusz Mazurek
Mateusz M.

Pokaż komentarze

  • Hej Mateusz,

    Czy mógłbyś napisać czym różni się wątek od procesu? Trochę poczytałem tą serię postów na twoim blogu ale nie wiem czy mi się ta wiedza utrwali. Może będę miał jakiś lepszy pogląd na to czego nie robić w Pythonie.

    Btw. Nie wiem co robi w komentarzach link do mojego postu o optymalizacjach w Pythonie. Pewnie pojawił się automatycznie.

    Pozdrawiam :-).

    • Jasne że mogę! Proces to instancja Twojego programu. Ma on wirtualną przestrzeń adresową, kod wykonywalny, otwarte uchwyty do obiektów systemowych, kontekst zabezpieczeń, unikalny identyfikator procesu, itp. Każdy proces jest uruchamiany z jednym wątkiem, nazywany wątkiem głównym, ale może tworzyć dodatkowe wątki. Wątki pracują na zasobach dostarczanych w ramach procesu, zakończenie wątku nie powoduje zakończenia procesu, ale zakończenie procesu powoduje zakończenie wątku. Jeśli masz np. edytor tekstu to w jednym wątku edytor dba o to by zapisywać co jakiś czas dokument, a w innym np. sprawdza na bieżąco pisownię:) Zamknięcie edytora zakończy sprawdzanie pisowni jak i automatyczne zapisywanie dokumentu.

      A link do Twojego posta to standardowy pingback:D i tak, robi się automatycznie:)

Ostatnie wpisy

Podsumowanie: maj, czerwiec, lipiec i sierpień 2024

Oj daaawnoo mnie tu nie było. Ale wakacje to był czas dużej liczby intensywnych wyjazdów i tak naprawdę, dopiero jakoś… Read More

2 miesiące ago

Podsumowanie: kwiecień 2024

Cześć! Zapraszam na krótkie podsumowanie kwietnia. Wyjazd do Niemiec A dokładniej pod granicę z Francją. Chrześnica miała pierwszą komunię. Po… Read More

6 miesięcy ago

Podsumowanie: luty i marzec 2024

Ostatnio tygodnie były tak bardzo wypełnione, że nie udało mi się napisać nawet krótkiego podsumowanie. Więc dziś zbiorczo podsumuję luty… Read More

7 miesięcy ago

Podsumowanie: styczeń 2024

Zapraszam na krótkie podsumowanie miesiąca. Książki W styczniu przeczytałem "Homo Deus: Historia jutra". Książka łudząco podoba do wcześniejszej książki tego… Read More

9 miesięcy ago

Podsumowanie roku 2023

Cześć! Zapraszam na podsumowanie roku 2023. Książki Zacznijmy od książek. W tym roku cel 35 książek nie został osiągnięty. Niemniej… Read More

10 miesięcy ago

Podsumowanie: grudzień 2023

Zapraszam na krótkie podsumowanie miesiąca. Książki W grudniu skończyłem czytać Mein Kampf. Nudna książka. Ciekawsze fragmenty można by było streścić… Read More

10 miesięcy ago