Czym różni się lista od tupli w Python


Często na rozmowach rekrutacyjnych może paść pytanie czym różni się lista od tupli w Python. Poznaj podstawowe cechy obu struktur.


Wprowadzenie

Ostatnio zostałem zapytany o różnicę między tuplą i listą. Przyznam szczerze, że trochę zbiło mnie to z rytmu, bo nie byłem w stanie z marszu opowiedzieć wszystkich różnic. Oczywiście wiedziałem, że obie struktury istnieją. Używałem obu podczas codziennej pracy i to z poprawnym ich przeznaczeniem. Znałem z grubsza różnicę ale nie potrafiłem ich dokładnie określić. Dlatego też postanowiłem napisać ten wpis, żeby w przyszłości inne osoby, które się z tym spotkają nie miały już tego problemu. Dla wielu osób spora część tekstu może być już znana ale zachęcam do przeczytania, bo może dowiecie się czegoś ciekawego? ;).
Zanim odpowiemy na pytanie czym różni się lista od tupli musimy zapoznać się z każdą z tych struktur z osobna. Zacznijmy więc od tupli.

Tuple w Python

Tuple, w zasadzie po polsku nazywane krotkami to niezmienny typ danych (ang. immutable). Co to właściwie znaczy? W dużym uproszczeniu można powiedzieć, że można z niej pobierać dane ale nie można ich modyfikować ani usuwać.  Powinna być zatem używana wszędzie tam, gdzie dane muszą być stałe. Tuple możemy rozróżnić po tym, że podczas jej tworzenia wykorzystujemy standardowe, okrągłe nawiasy () .

names = ('ddeby', 'listy', 'tuple')

W tupli dane można rozpakowywać:

a, b, c = names

W krotce można się również odwoływać bezpośrednio po indeksach:

names[0]  # 'ddeby'

Można również wycinać fragmenty krotki:

names[1:]   # 'listy', 'tuple'

Ostatecznie można też usunąć całą tuplę. Ale tylko całą:

del names

Listy w Python

Podstawową różnicą w stosunku do krotki, jest fakt iż listy są mutowalne, czyli w przeciwieństwie do poprzedniej struktury tu możemy dodawać nowe elementy i edytować, bądź usuwać istniejące. Dla rozróżnienia, do jej deklaracji używa się kwadratowych nawiasów [] .

names = ['ddeby', 'listy', 'tuple']

Listy można rozpakowywać:

a, b, c = names

Na liście można się również odwoływać bezpośrednio po indeksach:

names[0]  # 'ddeby'

Z listy można wycinać fragmenty danych:

names[1:]  # 'listy', 'tuple'

Jak widzimy lista ma dokładnie te same możliwości co tupla. Jest jednak nieco bardziej rozbudowana.

Lista jest mutowalna

Jak pisałem wcześniej  lista jest mutowalna. A to oznacza, że dane można edytować.
Możemy na przykład do listy dodawać nowe elementy:

names = ['ddeby', 'listy', 'tuple']
names.append('krotki') 
names  # ['ddeby', 'listy', 'tuple', 'krotki']

Można również edytować konkretny element:

names[1] = 'LISTY'
names  #  ['ddeby', 'LISTY', 'tuple', 'krotki']

Spróbujmy jeszcze usunąć element z listy:

del names[0]
names  # ['LISTY', 'tuple', 'krotki']

Możemy też pójść dalej i usunąć fragment listy:

del names[1:]
names  # ['LISTY']

Tupla jest niemutowalna

Spróbujemy wykonać te same operacje co wcześniej na tupli. To pokaże nam co znaczy, że tupla jest niemutowalna.

Dodanie elementu:

names.append('LISTY')  # AttributeError: 'tuple' object has no attribute 'append'

Edycja elementu:

names[1] = 'TUPLE'  # TypeError: 'tuple' object does not support item assignment

Usuwanie elementu:

del names[0]  # TypeError: 'tuple' object doesn't support item deletion

Funkcje

Można już zauważyć, że lista jest niejako rozszerzeniem tupli. Dlatego właśnie podstawowe funkcje dostępne w języku Python działają na obu tych strukturach. Są to min. len(), max(), min(), sum(), any(), all(), sorted() . Na przykład:

my_list = [1, 2, 4, 3, 7, 8, 9]                                                             
my_tuple = (1, 2, 4, 3, 7, 8, 9)                                                            
len(my_list)   #  7                                
len(my_tuple)   #  7

Metody

Idąc dalej tym tropem sprawdźmy jakie metody dostępne są w tupli a jakie na liście.
Tupla ma dostępne dwie publiczne metody i są to count() , która zwraca ile razy występuje element w zbiorcze oraz index() , informująca o pozycji występowania elementu.

data = (1, 2, 2, 3, 3, 4, 3, 4, 4, 4)
data.count(9)  # 0
data.count(3)  # 3
data.index(4)  # 5

Lista z kolei posiada znacznie więcej tych metod i są to głównie metody odpowiedzialne za modyfikację danych. Są to: append(), clear(), copy(), extend(), index(), insert(), pop(), remove(), reverse(), sort() na przykład:

my_list = [1, 2, 5, 3, 7, 4] 
my_list.sort()
my_list  # [1, 2, 3, 4, 5, 7]
my_list.pop(5)  # 7
my_list  # [1, 2, 3, 4, 5]

Podsumowanie:

Myślę, że teraz możemy sobie odpowiedzieć czym różnią się listy od tupli w Python. Podstawową różnicą jest fakt, że listy w przeciwieństwie do tupli są mutowalne. I to właśnie jest nasz punkt wyjścia. Listy bowiem dają znacznie więcej możliwości, przez edycję elementów. Tuple natomiast są bezpieczniejsze, bo mamy pewność, że dane nie zostaną zmodyfikowane. Dodatkowo z tupli można zbudować hash, a to bardzo ciekawa informacja, bo dzięki temu tuple można podać jako klucz w słowniku, a to informacja która nie jest oczywista.

names = ('ddeby', 'Dawid')
data = {names: 'developers'}  # data = {names: 'developers'}

Bonus:

Na koniec chciałbym podać jeszcze jeden ciekawy błąd z wykorzystaniem listy. Szerzej opiszę go w innym wpisie.
Czy poniższy kod jest poprawny? Albo czy widzicie  tym kodzie jakieś błędy?

def foo(data=[]):
    data.append('bar')
    return data

Teoretycznie wszystko jest OK. Pod warunkiem, że zawsze będziemy przekazywać parametr przy wywołaniu. Jeśli tego nie zrobimy ta lista z wartości domyślnej będzie współdzielona podczas życia programu, co możemy zauważyć na poniższym przykładzie:

foo()  # ['bar']
foo()  # ['bar', bar']
foo() # ['bar', bar', bar']

Niżej przykład jak zrobić to nieco lepiej:

def foo(data=None):
    data = [] if data is None else data
    data.append('bar')
    return data

foo()  # ['bar']
foo()  # ['bar']
foo()  # ['bar']

Macie pomysł na inne ciekawe tematy, które warto poruszyć?

May 19, 2019

Najnowsze wpisy

Zobacz wszystkie