Почему в списке нет безопасного метода «get», такого как словарь?

264

Почему в списке нет безопасного метода get, такого как словарь?

>>> 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
источник
1
Списки используются для иных целей, чем словари. Get () не требуется для типичных случаев использования списка. Однако для словаря get () довольно часто полезна.
mgronber
42
Вы всегда можете получить пустой подсписок из списка, не вызывая IndexError, если вместо l[10:11]этого попросите срез: вместо l[10], например. () У подсписка будет нужный элемент, если он существует)
jsbueno
56
Вопреки некоторым здесь, я поддерживаю идею сейфа .get. Это было бы эквивалентом l[i] if i < len(l) else default, но более читабельным, более кратким и позволяющим iбыть выражением без необходимости пересчитывать его
Пол Дрейпер,
6
Сегодня я хотел, чтобы это существовало. Я использую дорогую функцию, которая возвращает список, но я хотел только первый элемент, или Noneесли он не существует. Было бы неплохо сказать, x = expensive().get(0, None)чтобы мне не пришлось помещать бесполезный возврат дорогого во временную переменную.
Райан Хиберт
2
@ Райан, мой ответ может помочь вам stackoverflow.com/a/23003811/246265
Джейк,

Ответы:

112

В конечном счете, он, вероятно, не имеет безопасного .getметода, потому что a dictявляется ассоциативной коллекцией (значения связаны с именами), где неэффективно проверять наличие ключа (и возвращать его значение), не вызывая исключения, в то время как это супер тривиально чтобы исключить доступ к элементам списка (так как lenметод очень быстрый). .get метод позволяет вам запрашивать значение, связанное с именем, а не напрямую обращаться к 37-му элементу в словаре (что будет больше похоже на то, что вы запрашиваете в своем списке).

Конечно, вы можете легко реализовать это самостоятельно:

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

Вы могли бы даже добавить его в __builtins__.listконструктор __main__, но это было бы менее распространенным изменением, так как большая часть кода не использует его. Если вы просто хотите использовать это со списками, созданными вашим собственным кодом, вы можете просто создать подкласс listи добавить getметод.

Ник Бастин
источник
24
Python не позволяет встроенные типы обезьянных патчей, такие какlist
Imran
7
@CSZ: .getрешает проблему, которой нет в списках - эффективный способ избежать исключений при получении данных, которые могут не существовать. Очень просто и очень эффективно знать, что такое действительный индекс списка, но нет особенно хорошего способа сделать это для ключевых значений в словаре.
Ник Бастин
10
Я вообще не думаю, что речь идет об эффективности - проверка наличия ключа в словаре и / или возврат элемента O(1). С lenточки зрения сложности это будет не так быстро, как проверка , но с точки зрения сложности они все O(1). Правильный ответ - типичное использование / семантика ...
Марк Лонгэйр
3
@Mark: не все O (1) созданы равными. Кроме того, dictэто только лучший вариант O (1), а не все случаи.
Ник Бастин
4
Я думаю, что люди здесь упускают смысл. Обсуждение не должно быть об эффективности. Пожалуйста, остановитесь на преждевременной оптимизации. Если ваша программа работает слишком медленно, вы либо злоупотребляете, .get()либо у вас есть проблемы в другом месте вашего кода (или среды). Смысл использования такого метода - читабельность кода. Техника «ванили» требует четыре строки кода в каждом месте, что нужно сделать. Метод .get()требует только одного и может быть легко связан с последующими вызовами методов (например my_list.get(2, '').uppercase()).
Тайлер Кромптон
68

Это работает, если вы хотите первый элемент, как my_list.get(0)

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

Я знаю, что это не совсем то, что вы просили, но это может помочь другим.

Джейк
источник
7
менее питон, чем функциональное программирование
Эрик
next(iter(my_list[index:index+1]), 'fail')Позволяет для любого индекса, а не только 0. Или меньше FP , но , возможно , более Pythonic, и почти наверняка более читаемым: my_list[index] if index < len(my_list) else 'fail'.
алфавитное представление
47

Возможно, потому что это не имело особого смысла для семантики списка. Тем не менее, вы можете легко создать свой собственный, используя подклассы.

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()
Кит
источник
5
Это, безусловно, самый питонический ответ ОП. Обратите внимание, что вы также можете извлечь подсписок, который является безопасной операцией в Python. Учитывая mylist = [1, 2, 3], вы можете попытаться извлечь 9-й элемент с помощью mylist [8: 9], не вызывая исключения. Затем вы можете проверить, является ли список пустым, и, если он не пуст, извлечь отдельный элемент из возвращенного списка.
jose.angel.jimenez
1
Это должен быть принятый ответ, а не другие непитоновые однострочные хаки, особенно потому, что он сохраняет симметрию со словарями.
Эрик
1
Нет ничего питонного в том, чтобы создавать подклассы для ваших собственных списков только потому, что вам нужен хороший getметод. Читаемость имеет значение. И читаемость страдает с каждым дополнительным ненужным классом. Просто используйте try / exceptподход без создания подклассов.
Jeyekomon
@Jeyekomon Это совершенно Pythonic, чтобы сократить шаблон путем подклассов.
Кит
42

Вместо использования .get, использование как это должно быть хорошо для списков. Просто разница в использовании.

>>> l = [1]
>>> l[10] if 10 < len(l) else 'fail'
'fail'
ТЫ
источник
15
Это не удастся, если мы попытаемся получить последний элемент с -1.
претобомба
Обратите внимание, что это не работает для объектов списка с круговой связью. Кроме того, синтаксис вызывает то, что я люблю называть «блоком сканирования». При сканировании кода, чтобы увидеть, что он делает, эта строка на мгновение замедлит меня.
Тайлер Кромптон
встроенный, если / else не работает с более старым питоном как 2.6 (или это 2.5?)
Эрик
3
@TylerCrompton: в питоне нет циклически связанного списка. Если вы написали один самостоятельно, вы можете реализовать .getметод (за исключением того, что я не уверен, как бы вы объяснили, что означал индекс в этом случае или почему он никогда не будет работать).
Ник Бастин
Альтернатива, которая обрабатывает недопустимые отрицательные индексы, была быlst[i] if -len(lst) <= i < len(l) else 'fail'
mic
17

Попробуй это:

>>> i = 3
>>> a = [1, 2, 3, 4]
>>> next(iter(a[i:]), 'fail')
4
>>> next(iter(a[i + 1:]), 'fail')
'fail'
Всеволод Кулага
источник
1
Мне нравится этот, хотя сначала он требует создания нового подсписка.
Рик поддерживает Монику
15

Кредиты для jose.angel.jimenez


Для фанатов "oneliner" ...


Если вам нужен первый элемент списка или если вы хотите использовать значение по умолчанию, если список пуст, попробуйте:

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

возвращается a

и

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

возвращается default


Примеры для других элементов ...

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

С отступлением по умолчанию ...

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

Протестировано с Python 3.6.0 (v3.6.0:41df79263a11, Dec 22 2016, 17:23:13)

qräbnö
источник
1
Короткий вариант: value, = liste[:1] or ('default',). Похоже, вам нужны скобки.
Кребно
14

Лучшее, что вы можете сделать, это преобразовать список в dict, а затем получить к нему доступ с помощью метода 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')
потрясающий
источник
20
Разумный обходной путь, но вряд ли «лучшее, что вы можете сделать».
tripleee
3
Хотя очень неэффективно. Примечание: вместо этого zip range lenможно было просто использоватьdict(enumerate(my_list))
Marian
3
Это не лучшая вещь, это худшая вещь, которую ты можешь сделать.
erikbwork
3
Это хуже всего, если вы рассматриваете производительность ... если вы заботитесь о производительности, вы не пишете код на интерпретируемом языке, таком как python. Я нахожу это решение, используя словарь довольно элегантным, мощным и питоническим. Ранняя оптимизация в любом случае является злом, поэтому давайте определимся и увидим позже, что это узкое место.
Эрик
7

Поэтому я провел еще несколько исследований по этому вопросу, и оказалось, что для этого нет ничего конкретного. Я был взволнован, когда нашел list.index (значение), он возвращает индекс указанного элемента, но нет ничего для получения значения по определенному индексу. Так что если вы не хотите использовать решение safe_list_get, которое я считаю довольно хорошим. Вот некоторые 1 линейные операторы if, которые могут выполнить работу за вас в зависимости от сценария:

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

Вы также можете использовать «Нет» вместо «Нет», что имеет больше смысла.

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

Также, если вы хотите просто получить первый или последний элемент в списке, это работает

end_el = x[-1] if x else None

Вы также можете превратить их в функции, но мне все равно понравилось решение исключений IndexError. Я экспериментировал с фиктивной версией safe_list_getрешения и сделал ее немного проще (по умолчанию нет):

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

Не тестировали, чтобы увидеть, что быстрее.

radtek
источник
1
Не совсем питонический.
Эрик
@ Эрик, какой фрагмент? Я думаю, что попытка, за исключением, имеет больше смысла, глядя на это снова.
Радтек
Автономная функция не является питонической. Исключения немного более питонны, но не настолько, как это часто встречается в языках программирования. Более того, pythonic - это новый объект, который расширяет встроенный тип list, создавая его подклассы. Таким образом, конструктор может взять listили что-нибудь, что ведет себя как список, а новый экземпляр ведет себя как a list. См. Ответ Кита ниже, который должен быть принятым ИМХО.
Эрик
1
@Eric Я проанализировал вопрос не как специфичный для ООП, а как «почему списки не имеют dict.get()аналога для возврата значения по умолчанию из ссылки на индекс списка вместо необходимости отлавливать IndexError? Так что на самом деле речь идет о функции языка / библиотеки (а не ООП»). против контекста FP). Кроме того, вероятно, нужно квалифицировать ваше использование «pythonic» как, возможно, WWGD (поскольку его презрение к FP Python хорошо известно) и не обязательно просто удовлетворение PEP8 / 20.
Cowbert
1
el = x[4] if len(x) == 4 else 'No'- ты имеешь в виду len(x) > 4? x[4]вне границ, если len(x) == 4.
микрофон
4

Словари для поиска. Имеет смысл спросить, существует ли запись или нет. Списки обычно повторяются. Часто спрашивают, существует ли L [10], а скорее, если длина L равна 11.

Очередь ученика
источник
Да, согласен с тобой. Но я только что проанализировал относительный URL страницы "/ group / Page_name". Разделите его на «/» и хотите проверить, равно ли PageName определенной странице. Было бы удобно написать что-то вроде [url.split ('/'). Get_from_index (2, None) == "lalala"] вместо того, чтобы делать дополнительную проверку длины или ловить исключение или писать собственную функцию. Вероятно, вы правы, это просто считается необычным. Во всяком случае, я до сих пор не согласен с этим =)
CSZ
@ Ник Бастин: Ничего плохого. Все дело в простоте и скорости кодирования.
CSZ
Было бы также полезно, если бы вы хотели использовать списки в качестве более компактного словаря в тех случаях, когда ключи являются последовательными целочисленными значениями. Конечно, существование отрицательной индексации уже останавливает это.
Сурьма
-1

Ваш сценарий использования в основном важен только для массивов и матриц фиксированной длины, чтобы вы знали, как долго они находятся перед этим. В этом случае вы, как правило, также создаете их, прежде чем вручную заполнять их None или 0, так что фактически любой индекс, который вы будете использовать, уже существует.

Вы могли бы сказать это: мне нужно .get () в словарях довольно часто. После десяти лет работы программистом на полную ставку, я не думаю, что мне когда-либо это нужно было в списке. :)

Леннарт Регебро
источник
Как насчет моего примера в комментариях? Что является более простым и читабельным? (url.split ('/'). getFromIndex (2) == "lalala") ИЛИ (result = url.split (); len (result)> 2 и result [2] == "lalala"). И да, я знаю, что могу написать такую ​​функцию сам =) но я был удивлен, что такая функция не встроена.
CSZ
1
Я бы сказал, что в вашем случае вы делаете это неправильно. Обработка URL должна выполняться либо путем маршрутов (сопоставления с образцом), либо путем обхода объекта. Но, чтобы ответить на ваш конкретный случай: 'lalala' in url.split('/')[2:]. Но проблема с вашим решением в том, что вы смотрите только на второй элемент. Что делать, если URL-адрес '/ monkeybonkey / lalala'? Вы получите, Trueхотя URL-адрес недействителен.
Леннарт Регебро
Я взял только второй элемент, потому что мне нужен был только второй элемент. Но да, ломтики кажутся хорошей рабочей альтернативой
CSZ
@CSZ: Но тогда первый элемент игнорируется, и в этом случае вы можете пропустить его. :) Посмотрите, что я имею в виду, пример не очень хорошо работает в реальной жизни.
Леннарт Регебро