Dlaczego lista nie ma bezpiecznej metody „get”, takiej jak słownik?

264

Dlaczego lista nie ma bezpiecznej metody „get”, takiej jak słownik?

>>> d = {'a':'b'}
>>> d['a']
'b'
>>> d['c']
KeyError: 'c'
>>> d.get('c', 'fail')
'fail'

>>> l = [1]
>>> l[10]
IndexError: list index out of range
CSZ
źródło
1
Listy są wykorzystywane do innych celów niż słowniki. Funkcja get () nie jest potrzebna w typowych przypadkach użycia listy. Jednak w przypadku słownika metoda get () jest dość często przydatna.
mgronber
42
Zawsze możesz pobrać pustą podlistę z listy bez podnoszenia IndexError, jeśli zamiast l[10:11]tego poprosisz o plasterek: zamiast l[10], na przykład. () Podlista będzie miała pożądany element, jeśli istnieje)
jsbueno
56
W przeciwieństwie do niektórych tutaj popieram pomysł sejfu .get. Byłby to odpowiednik l[i] if i < len(l) else default, ale bardziej czytelny, bardziej zwięzły i pozwalający na iwyrażenie bez konieczności ponownego obliczania
Paul Draper,
6
Dzisiaj chciałbym, żeby to istniało. Używam drogiej funkcji, która zwraca listę, ale chciałem tylko pierwszego elementu lub Nonejeśli nie istniał. Byłoby miło powiedzieć x = expensive().get(0, None), że nie musiałbym umieszczać bezużytecznego zwrotu drogiego w zmiennej tymczasowej.
Ryan Hiebert
2
@ Ryan moja odpowiedź może ci pomóc stackoverflow.com/a/23003811/246265
Jake

Odpowiedzi:

112

Ostatecznie prawdopodobnie nie ma bezpiecznej .getmetody, ponieważ a dictjest kolekcją asocjacyjną (wartości są powiązane z nazwami), w której nieefektywne jest sprawdzenie, czy klucz jest obecny (i zwrócenie jego wartości) bez zgłaszania wyjątku, podczas gdy jest on bardzo trywialny aby uniknąć wyjątków dostępu do elementów listy (ponieważ lenmetoda jest bardzo szybka). Ta .getmetoda umożliwia zapytanie o wartość związaną z nazwą, a nie bezpośredni dostęp do 37 pozycji w słowniku (co byłoby bardziej podobne do tego, o co pytasz z listy).

Oczywiście możesz to łatwo zaimplementować samodzielnie:

def safe_list_get (l, idx, default):
  try:
    return l[idx]
  except IndexError:
    return default

Możesz nawet dopasować go do __builtins__.listkonstruktora w __main__, ale byłaby to mniej powszechna zmiana, ponieważ większość kodu go nie używa. Jeśli chcesz tego użyć z listami utworzonymi przez Twój własny kod, możesz po prostu podklasę listi dodać getmetodę.

Nick Bastin
źródło
24
Python nie zezwala na wbudowane typy monkeypatchingu, takie jaklist
Imran
7
@CSZ: .getrozwiązuje problem, którego nie ma na listach - skuteczny sposób na uniknięcie wyjątków podczas pobierania danych, które mogą nie istnieć. Bardzo trywialne i bardzo skuteczne jest sprawdzenie, czym jest prawidłowy indeks listy, ale nie ma szczególnie dobrego sposobu na zrobienie tego dla kluczowych wartości w słowniku.
Nick Bastin
10
Nie sądzę, żeby w ogóle chodziło o wydajność - sprawdzanie, czy klucz jest obecny w słowniku i / lub czy zwracany jest przedmiot O(1). Z grubsza nie będzie to tak szybkie jak sprawdzanie len, ale ze złożonego punktu widzenia są one wszystkie O(1). Prawidłowa odpowiedź to typowe zastosowanie / semantyka ...
Mark Longair,
3
@ Mark: Nie wszystkie O (1) są sobie równe. Ponadto dictjest to tylko najlepszy przypadek O (1), nie wszystkie przypadki.
Nick Bastin
4
Myślę, że ludziom brakuje tutaj sensu. Dyskusja nie powinna dotyczyć wydajności. Przestań z przedwczesną optymalizacją. Jeśli twój program jest zbyt wolny, albo nadużywasz, .get()albo masz problemy w innym miejscu w kodzie (lub środowisku). Istotą takiej metody jest czytelność kodu. Technika „waniliowa” wymaga czterech wierszy kodu w każdym miejscu, w którym należy to zrobić. Ta .get()technika wymaga tylko jednego i może być łatwo połączona za pomocą kolejnych wywołań metod (np my_list.get(2, '').uppercase().).
Tyler Crompton
67

Działa to, jeśli chcesz pierwszego elementu, takiego jak my_list.get(0)

>>> my_list = [1,2,3]
>>> next(iter(my_list), 'fail')
1
>>> my_list = []
>>> next(iter(my_list), 'fail')
'fail'

Wiem, że nie jest to dokładnie to, o co prosiłeś, ale może pomóc innym.

Jake
źródło
7
mniej pythoniczny niż funkcjonalny programistyczny
Eric
next(iter(my_list[index:index+1]), 'fail')Pozwala na każdym indeksie, a nie tylko mniej lub 0. FP ale zapewne bardziej pythonowy, a prawie na pewno bardziej czytelny: my_list[index] if index < len(my_list) else 'fail'.
alphabetasoup
47

Prawdopodobnie dlatego, że nie miało to większego sensu dla semantyki listy. Możesz jednak łatwo utworzyć własne, podklasując.

class safelist(list):
    def get(self, index, default=None):
        try:
            return self.__getitem__(index)
        except IndexError:
            return default

def _test():
    l = safelist(range(10))
    print l.get(20, "oops")

if __name__ == "__main__":
    _test()
Keith
źródło
5
Jest to zdecydowanie najbardziej pytoniczny sposób odpowiadania na PO. Zauważ, że możesz także wyodrębnić listę podrzędną, która jest bezpieczną operacją w Pythonie. Biorąc pod uwagę mylist = [1, 2, 3], możesz spróbować wyodrębnić 9 element za pomocą mylist [8: 9] bez wyzwalania wyjątku. Następnie możesz sprawdzić, czy lista jest pusta, a jeśli nie jest pusta, wyodrębnij pojedynczy element ze zwróconej listy.
jose.angel.jimenez
1
To powinna być zaakceptowana odpowiedź, a nie inne nie Pythońskie hacki jednowarstwowe, szczególnie dlatego, że zachowuje symetrię ze słownikami.
Eric
1
W podklasowaniu własnych list nie ma nic pythonicznego tylko dlatego, że potrzebujesz dobrej getmetody. Liczy się czytelność. A czytelność cierpi z każdą dodatkową niepotrzebną klasą. Po prostu zastosuj try / exceptpodejście bez tworzenia podklas.
Jeyekomon
@Jeyekomon Idealnie Pythonic redukuje płytę kotłową poprzez podklasę.
Keith,
42

Zamiast używać .get, używanie w ten sposób powinno być odpowiednie dla list. Różnica w użyciu.

>>> l = [1]
>>> l[10] if 10 < len(l) else 'fail'
'fail'
TY
źródło
15
To się nie powiedzie, jeśli spróbujemy uzyskać najnowszy element z -1.
pretobomba
Zauważ, że to nie działa dla okrągłych obiektów listy. Dodatkowo składnia powoduje, co lubię nazywać „blokiem skanowania”. Podczas skanowania kodu, aby zobaczyć, co robi, jest to linia, która spowolniłaby mnie na chwilę.
Tyler Crompton,
inline, jeśli / else nie działa ze starszym pythonem, takim jak 2.6 (czy to jest 2.5?)
Eric
3
@TylerCrompton: W Pythonie nie ma cyklicznie połączonej listy. Jeśli sam go napisałeś, możesz wdrożyć .getmetodę (z wyjątkiem tego, że nie jestem pewien, w jaki sposób wyjaśniłbyś, co oznaczał indeks w tym przypadku lub dlaczego miałby się nie powieść).
Nick Bastin
Alternatywą, która obsługuje indeksy ujemne poza zakresem, byłbylst[i] if -len(lst) <= i < len(l) else 'fail'
mic
17

Spróbuj tego:

>>> i = 3
>>> a = [1, 2, 3, 4]
>>> next(iter(a[i:]), 'fail')
4
>>> next(iter(a[i + 1:]), 'fail')
'fail'
Vsevolod Kulaga
źródło
1
podoba mi się ten, choć najpierw wymaga utworzenia nowej listy podrzędnej.
Rick wspiera Monikę
15

Kredyty dla jose.angel.jimenez


Dla fanów „oneliner”…


Jeśli chcesz pierwszy element listy lub jeśli chcesz wartość domyślną, jeśli lista jest pusta, spróbuj:

liste = ['a', 'b', 'c']
value = (liste[0:1] or ('default',))[0]
print(value)

zwroty a

i

liste = []
value = (liste[0:1] or ('default',))[0]
print(value)

zwroty default


Przykłady innych elementów…

liste = ['a', 'b', 'c']
print(liste[0:1])  # returns ['a']
print(liste[1:2])  # returns ['b']
print(liste[2:3])  # returns ['c']

Z domyślną rezerwą…

liste = ['a', 'b', 'c']
print((liste[0:1] or ('default',))[0])  # returns a
print((liste[1:2] or ('default',))[0])  # returns b
print((liste[2:3] or ('default',))[0])  # returns c

Testowane z Python 3.6.0 (v3.6.0:41df79263a11, Dec 22 2016, 17:23:13)

qräbnö
źródło
1
Krótki alternatywa: value, = liste[:1] or ('default',). Wygląda na to, że potrzebujesz nawiasów.
qräbnö
13

Najlepszą rzeczą, jaką możesz zrobić, to przekształcić listę w dykt, a następnie uzyskać do niej dostęp za pomocą metody get:

>>> my_list = ['a', 'b', 'c', 'd', 'e']
>>> my_dict = dict(enumerate(my_list))
>>> print my_dict
{0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e'}
>>> my_dict.get(2)
'c'
>>> my_dict.get(10, 'N/A')
fab
źródło
20
Rozsądne obejście, ale nie jest „najlepszą rzeczą, jaką możesz zrobić”.
tripleee
3
Jednak bardzo nieefektywna. Uwaga: Zamiast tego zip range lenmożna po prostu użyćdict(enumerate(my_list))
Marian
3
To nie jest najlepsza rzecz, to najgorsza rzecz, jaką możesz zrobić.
erikbwork
3
To najgorsze, jeśli wziąć pod uwagę wydajność ... jeśli zależy Ci na wydajności, nie kodujesz w interpretowanym języku, takim jak python. Uważam to rozwiązanie za pomocą słownika raczej eleganckiego, potężnego i pytonicznego. Wczesne optymalizacje i tak są złe, więc zróbmy dyktę, a później zobaczymy, że to wąskie gardło.
Eric
7

Zrobiłem więc więcej badań na ten temat i okazało się, że nie ma w tym nic konkretnego. Byłem podekscytowany, gdy znalazłem list.index (wartość), zwraca indeks określonego elementu, ale nie ma nic do uzyskania wartości przy określonym indeksie. Więc jeśli nie chcesz używać rozwiązania safe_list_get, które moim zdaniem jest całkiem dobre. Oto 1 linijka, jeśli instrukcje, które mogą wykonać zadanie dla Ciebie w zależności od scenariusza:

>>> x = [1, 2, 3]
>>> el = x[4] if len(x) > 4 else 'No'
>>> el
'No'

Możesz także użyć None zamiast „No”, co ma większy sens:

>>> x = [1, 2, 3]
>>> i = 2
>>> el_i = x[i] if len(x) == i+1 else None

Również jeśli chcesz dostać pierwszy lub ostatni element na liście, to działa

end_el = x[-1] if x else None

Możesz również przekształcić je w funkcje, ale nadal podobało mi się rozwiązanie wyjątku IndexError. Eksperymentowałem z fałszywą wersją safe_list_getrozwiązania i uczyniłem go nieco prostszym (brak domyślnego):

def list_get(l, i):
    try:
        return l[i]
    except IndexError:
        return None

Nie przeprowadziłem testów porównawczych, aby zobaczyć, co jest najszybsze.

radtek
źródło
1
Nie bardzo pytoniczny.
Eric
@ Eric, który fragment kodu? Myślę, że próba, z wyjątkiem tego, że ma sens, patrząc na nią jeszcze raz.
radtek
Autonomiczna funkcja nie jest pythoniczna. Wyjątki są rzeczywiście trochę bardziej pytoniczne, ale nie za bardzo, ponieważ jest to tak powszechny wzorzec w językach programowania. Co więcej, Python jest nowym obiektem, który rozszerza wbudowany typ listpoprzez jego podklasowanie. W ten sposób konstruktor może pobrać listdowolną listę, która zachowuje się jak lista, a nowa instancja zachowuje się jak list. Zobacz odpowiedź Keitha poniżej, która powinna być zaakceptowana przez IMHO.
Eric
1
@Eric przeanalizowałem pytanie nie jako specyficzne dla OOP, ale jako „dlaczego listy nie mają analogii do dict.get()zwracania wartości domyślnej z odwołania do indeksu listy zamiast konieczności przechwytywania IndexError? Więc tak naprawdę chodzi o funkcję języka / biblioteki (a nie OOP kontra kontekst FP) Ponadto prawdopodobnie należy zakwalifikować użycie słowa „python” jako być może WWGD (ponieważ jego pogarda dla FP Python jest dobrze znana) i niekoniecznie po prostu satysfakcjonująca PEP8 / 20.
cowbert
1
el = x[4] if len(x) == 4 else 'No'- masz na myśli len(x) > 4? x[4]jest poza zakresem jeśli len(x) == 4.
mic
4

Słowniki służą do wyszukiwania. Warto zapytać, czy wpis istnieje, czy nie. Listy są zwykle iterowane. Często zdarza się pytanie, czy L [10] istnieje, ale czy długość L wynosi 11.

Kolejka praktykantów
źródło
Tak, zgadzam się z tobą. Ale właśnie przeanalizowałem względny adres URL strony „/ group / Page_name”. Podziel go na „/” i chciałem sprawdzić, czy nazwa strony jest równa określonej stronie. Wygodne byłoby napisanie czegoś takiego jak [url.split ('/'). Get_from_index (2, None) == "lalala"] zamiast dodatkowego sprawdzania długości lub wyjątku catch lub pisania własnej funkcji. Prawdopodobnie masz rację, jest to po prostu uważane za niezwykłe. W każdym razie nadal nie zgadzam się z tym =)
CSZ
@Nick Bastin: Nic złego. Chodzi o prostotę i szybkość kodowania.
CSZ
Przydałby się również, gdybyś chciał używać list jako słownika zajmującego więcej miejsca w przypadkach, gdy klucze są kolejnymi liczbami całkowitymi. Oczywiście istnienie ujemnego indeksowania już to zatrzymuje.
Antimony
-1

Twój przypadek użycia jest w zasadzie istotny tylko przy wykonywaniu tablic i matryc o stałej długości, abyś wiedział, jak długo mają one przed sobą. W takim przypadku zazwyczaj tworzysz je również przed ręcznym wypełnieniem ich None lub 0, aby w rzeczywistości każdy indeks, którego użyjesz, już istniał.

Można powiedzieć tak: dość często potrzebuję .get () w słownikach. Po dziesięciu latach pełnoetatowego programisty nie sądzę, żebym kiedykolwiek potrzebował go na liście. :)

Lennart Regebro
źródło
Co powiesz na mój przykład w komentarzach? Co jest prostsze i czytelniejsze? (url.split ('/'). getFromIndex (2) == "lalala") LUB (wynik = url.split (); len (wynik)> 2 i wynik [2] == "lalala"). I tak, wiem, że sam mogę napisać taką funkcję =), ale byłem zaskoczony, że taka funkcja nie jest wbudowana.
CSZ
1
Mówię w twoim przypadku, że robisz to źle. Obsługa adresów URL powinna odbywać się albo przez trasy (dopasowanie wzorca), albo przez przechodzenie przez obiekt. Jednak, aby odpowiedzieć na konkretny przypadek: 'lalala' in url.split('/')[2:]. Problem w tym rozwiązaniu polega na tym, że patrzysz tylko na drugi element. Co jeśli adres URL to „/ monkeybonkey / lalala”? Otrzymasz komunikat, Truemimo że adres URL jest nieprawidłowy.
Lennart Regebro
Wziąłem tylko drugi element, ponieważ potrzebowałem tylko drugiego elementu. Ale tak, plastry wydają się dobrą działającą alternatywą
CSZ
@CSZ: Ale wtedy pierwszy element jest ignorowany, w takim przypadku można go pominąć. :) Zobacz, co mam na myśli, przykład nie działa tak dobrze w prawdziwym życiu.
Lennart Regebro