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 :)
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
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
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
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!:)