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.
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.
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!
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:
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 :)
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
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
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
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
Cześć, ostatnio w Internecie pojawiło się dużo artykułów, które nie były przychylne pracy zdalnej. Z drugiej strony większość komentarzy pod… Read More
Zapraszam na krótkie podsumowanie miesiąca. Książki W listopadzie dokończyłem cykl "Z mgły zrodzony" Sandersona. Tylko "Stop prawa" mi nie do… Read More
Pokaż komentarze
Trochę już w Pythonie programuję, ale dzięki tej serii widzę co mogę robić lepiej :)
Dzięki za docenienie! To wiele znaczy :)
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
Heloł! Myślę że tak proste optymalizacje jak ta to raczej kwestia bardziej czystego kodu i świadomości niż myślenia tylko o czasie wykonywania kodu:) ja bym chyba nawet nie używał słowa "optymalizacja" a bardziej "dobra praktyka". To trochę jak wbijanie gwoździa - możesz to zrobić śrubokrętem ale wygodniej będzie młotkiem:)
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.
Rozpakowywanie listy można też wykonać w następujący sposób (przydatne np. do przechowywania kompaktowo wartości stałych):
Tak!:)