Mateusz Mazurek – programista z pasją

Python, architektura, ciekawostki ze świata IT

Programowanie Programowanie webowe

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

Witam w trzecim artykule z serii dotyczącej tego, czego nie powinniśmy robić w Pythonie. Załączam dwa poprzednie wpisy:

i bez przydługiego wstępu, przejdźmy do treści.

Z rozwagą używajmy metody insert z obiektu listy

Metoda insert(index, element) pozwala dodać do listy element na konkretnym miejscu (indeksie). Zerknijmy na kod:

1
2
3
4
5
6
numbers = []

numbers.insert(0, 'jeden')
numbers.insert(len(numbers), 'dwa')

print(numbers)

Ponieważ najpierw dodaliśmy do listy na indeksie zerowym słowo „jeden”, a potem na indeksie równym długości listy (a więc tu na pierwszym) słowo „dwa”, wobec tego uzyskany wynik to:

['jeden', 'dwa']

I to wszystko jest pewnie jasne.

To przed czym chciałbym przestrzec, to sytuacja w której dodajemy metodą insert jakiś element na zerowy indeks, ponieważ Python będzie zmuszony do przesunięcia reszty zawartości tej listy.

1
2
3
4
5
6
numbers = []

numbers.insert(0, 'jeden')
numbers.insert(0, 'dwa')

print(numbers)

wynik wykonania się tego kodu to

['dwa', 'jeden']

Czemu w takim razie nie powinno się używać tej metody do dodawania elementów na początek listy? Bo to jest bardzo nieefektywne rozwiązanie.

Jeśli piszesz swój kod i już wiesz, że będziesz potrzebował dodawać elementy na początek listy, radzę skorzystać z obiektu deque. Wykorzystajmy timeit’a i zmierzmy czas wykonania się bliźniaczych operacji dla listy i dla deque (double-ended queue czyli lista dwukierunkowa):

1
2
3
4
from timeit import timeit

print(timeit('s.appendleft(37)', 'import collections; s = collections.deque()', number=100000))
print(timeit('s.insert(0,37)', 's = []', number=100000))

wykonanie takiego kodu da rezultat:

0.007789244002196938 # dla deque
2.211549069004832 # dla listy

Różnica wynika ze złożoności obliczeniowej obu rozwiązań, a złożoność obliczeniowa ze sposobu implementacji ich w Pythonie. Złożoność obliczeniowa dla list.insert() jest liniowa, a dla deque.appendleft() jest stała, czyli niezależna od ilości elementów.


Czekaj, stop!

Podoba Ci się to co tworzę? Jeśli tak to zapraszam Cię do zapisania się na newsletter:
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 :)
Postaw mi kawę na buycoffee.to

Brak rozpakowywania list

Czasem potrzebujemy przypisać elementy z listy do zmiennych, by móc ich potem używać. Możemy to zrobić tak:

1
2
3
4
5
6
7
some_values = ['jeden', 2, 'three']

first = some_values[0]
second = some_values[1]
third = some_values[2]

print(first, second, third)

co oczywiście – zadziała, ale jest to bardzo mało Pythonowe rozwiązanie. Możesz zrobić to lepiej w taki sposób:

1
2
3
4
5
some_values = ['jeden', 2, 'three']

first, second, third = some_values

print(first, second, third)

Czemu tak? Bo to ładne i przede wszystkim Python’owe rozwiązanie. Wynik obu rozwiązań jest taki sam!

Używanie listy do kolekcji elementów w których będziemy tylko szukać

Najlepiej sytuację o której myślę pokaże kawałek kodu:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Shop:
    STATES = ['open', 'closed', 'temporary_closed', 'nearly_closed']

    def __init__(self, state):
        self.state = state

    def has_valid_state(self):
        return self.state in self.STATES


s = Shop('open')

print(s.has_valid_state())

Klasa sklepu ma swoje możliwe stany i w oparciu o wartość pola zwracamy informację, czy aktualny stan jest poprawny. Na 100% widziałeś podobny kod wiele razy.

To podejście oczywiście spełni swoją funkcję. Jest jednak nieoptymalne.

Jeśli chcemy tworzyć kolekcję, której jedynym zastosowaniem jest sprawdzanie, czy jakiś element w niej istnieje, np jak w klasie wyżej – do zawężenia możliwych stanów elementu, powinniśmy zrezygnować z listy. I użyć zbioru.

I znów, udowodnijmy to korzystając z timeit’a!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from timeit import timeit


setup1 = """
STATES = {"OPEN", "NEARLY_CLOSED", "NEARLY_OPEN", "TEMPORARY_CLOSED", "CLOSED"}
"""


setup2 = """
STATES = ["OPEN", "NEARLY_CLOSED", "NEARLY_OPEN", "TEMPORARY_CLOSED", "CLOSED"]
"""



print("Checking in set with not exists element: ", timeit("'NO_EXISTS' in STATES", setup=setup1, number=100000))
print("Checking in list with not exists element: ", timeit("'NO_EXISTS' in STATES", setup=setup2, number=100000))


print("Checking in set with exists element: ", timeit("'NEARLY_OPEN' in STATES", setup=setup1, number=100000))
print("Checking in list with exists element: ", timeit("'NEARLY_OPEN' in STATES", setup=setup2, number=100000))

i wynik kodu:

Checking in set with not exists element: 0.0025714500006870367
Checking in list with not exists element: 0.006017967999468965
Checking in set with exists element: 0.0023319259998970665
Checking in list with exists element: 0.0035892980004064157

Sprawdzamy tu 4 przypadki:

  • szukanie w zbiorze nie istniejącego elementu
  • szukanie w liście nie istniejącego elementu
  • szukanie w zbiorze istniejącego elementu
  • szukanie w liście istniejącego elementu

I, co widać po czasach, wygrywa zbiór. Zatem polecam używać go w sytuacjach kiedy rozważamy posiadanie kolekcji w której będziemy tylko szukać.

Tym wnioskiem kończymy ten wpis. Będą kolejne :)

Dzięki za wizytę,
Mateusz Mazurek

A może wolisz nowości na mail?

Subskrybuj
Powiadom o
guest

Witryna wykorzystuje Akismet, aby ograniczyć spam. Dowiedz się więcej jak przetwarzane są dane komentarzy.

7 komentarzy
Inline Feedbacks
View all comments
Adrian

Trochę już w Pythonie programuję, ale dzięki tej serii widzę co mogę robić lepiej :)

Kamil

Czy korzystanie z seta zamiast listy wygrywa tylko czas sprawdzania i to jeszcze nie rzędem a jakimś mniejszym przelicznikiem?

Jak dla mnie nie jest to argument za używaniem set’a w tym przypadku. Jeśli używamy pythona i myślimy tylko o czasie wykonywania kodu w ms… to używamy złego języka w mojej skromnej opinii :) Bardziej bym się zastanawiał wtedy czy nie używać enuma i sprawdzania typów na wejściu aniżeli bawił się w takie optymalizację kodu

Grzegorz M.

To jest elementarne dobieranie właściwej struktury danych. Optymalizacja prędkości wykonania jest jedynie produktem ubocznym i zakładam, że autor użył jej jako przykładu jednej z wielu korzyści płynących z dobrych praktyk programistycznych.

tewlwolow

Rozpakowywanie listy można też wykonać w następujący sposób (przydatne np. do przechowywania kompaktowo wartości stałych):

some_values = [1, 2, 3]

def sum_of_three(int1, int2, int3):
  return int1 + int2 + int3


print(sum_of_three(*some_values))