Чому в списку немає безпечного методу "отримати", як словник?


264

Чому в списку немає безпечного методу "отримати", як словник?

>>> 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

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
@Ryan моя відповідь може допомогти вам stackoverflow.com/a/23003811/246265
Джейк

Відповіді:


112

Зрештою, він, мабуть, не має безпечного .getметоду, оскільки 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 не дозволяє monkeypatching типів вбудованих якlist
Імран

7
@CSZ: .getвирішує проблему, якої у списках немає - ефективний спосіб уникнути винятків при отриманні даних, які можуть не існувати. Це дуже тривіально і дуже ефективно знати, що таке дійсний індекс списку, але для ключових значень у словнику немає особливо гарного способу.
Нік Бастін

10
Я взагалі не думаю, що це стосується ефективності - перевірка наявності ключа в словнику та / або повернення елемента O(1). Це буде не так швидко в сирому вигляді, як перевірка len, але з точки зору складності вони всі O(1). Правильна відповідь - типове використання / семантика ...
Марк Лонгейр,

3
@ Марк: Не всі O (1) створені рівними. Крім того, dictє лише найкращим випадком O (1), не у всіх випадках.
Нік Бастін

4
Я думаю, що тут люди пропускають суть. Дискусія не повинна стосуватися ефективності. Будь ласка, припиніть із передчасною оптимізацією. Якщо ваша програма занадто повільна, ви або зловживаєте, .get()або у вас є проблеми в іншому місці коду (або оточення). Сенс використання такого методу - читабельність коду. Техніка "ванілі" вимагає чотирьох рядків коду в кожному місці, що це потрібно зробити. .get()Метод вимагає тільки один і може бути легко прикутий з подальшими викликами методів (наприклад my_list.get(2, '').uppercase()).
Тайлер Кромптон

67

Це працює, якщо ви хочете перший елемент, наприклад my_list.get(0)

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

Я знаю, це не зовсім те, про що ви просили, але це може допомогти іншим.


7
менш пітонічний, ніж функціональне програмування-esque
Ерік

next(iter(my_list[index:index+1]), 'fail')Дозволяє для будь-якого індексу, а не тільки 0. Або менше FP , але , можливо , більш Pythonic, і майже напевно більш читабельним: my_list[index] if index < len(my_list) else 'fail'.
alphabetasoup

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. З огляду на мій список = [1, 2, 3], ви можете спробувати витягти 9-й елемент з моїм списком [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.
pretobomba

Зауважте, що це не працює для об'єктів списку, що мають круговий зв’язок. Крім того, синтаксис викликає те, що я люблю називати "блоком сканування". Під час сканування коду, щоб побачити, що він робить, це рядок, який на мить сповільнить мене.
Тайлер Кромптон

inline якщо / else не працює зі старшим пітоном, як-от 2.6 (чи це 2.5?)
Ерік

3
@TylerCrompton: У python немає циркулярно пов'язаного списку. Якщо ви написали його самостійно, ви можете впровадити .getметод (за винятком я не впевнений, як би ви пояснили, що означав індекс у цьому випадку чи чому він коли-небудь вийде з ладу).
Нік Бастін

Альтернатива, яка обробляє негативні показники поза межами,lst[i] if -len(lst) <= i < len(l) else 'fail'
мік


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)


1
Короткий варіант: value, = liste[:1] or ('default',). Схоже, вам потрібні дужки.
qräbnö

13

Найкраще, що ви можете зробити, це перетворити список у дикт, а потім отримати доступ до нього методом 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
Доцільне рішення, але навряд чи "найкраще, що ти можеш зробити".
трійчатка

3
Хоча дуже неефективно. Примітка. Замість цього zip range lenможна було б просто скористатисьdict(enumerate(my_list))
Мар’ян

3
Це не найкраща річ, це найгірше, що ти міг зробити.
erikbwork

3
Це найгірше, якщо ви вважаєте продуктивність ... якщо ви дбаєте про продуктивність, ви не кодуєте мову, що тлумачиться, як python. Я вважаю це рішення за допомогою словника досить елегантного, потужного та пітонічного. Ранні оптимізації в будь-якому випадку є злими, тому давайте складемо вислів і побачимо згодом це вузьке місце.
Ерік

7

Тож я провів ще кілька досліджень цього, і виявляється, для цього немає нічого конкретного. Я схвильований, коли знайшов list.index (value), він повертає індекс визначеного елемента, але нічого не може отримати значення за певним індексом. Тож якщо ви не хочете використовувати рішення safe_list_get, яке, на мою думку, є досить хорошим. Ось 1 вкладиш, якщо заяви, які можуть виконати роботу за вас залежно від сценарію:

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

Ви також можете використовувати None замість "Ні", що має більше сенсу.

>>> 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

Не перевіряли те, що найшвидше.


1
Не дуже пітонічний.
Ерік

@Eric який фрагмент? Я думаю, що спробувати, окрім як має найбільше сенсу, переглянувши її ще раз.
radtek

Автономна функція не є пітонічною. Винятки справді трохи пітонічніші, але не настільки, оскільки це така поширена модель у мовах програмування. Що набагато пітонічніше - це новий об’єкт, який розширює вбудований тип list, підкласуючи його. Таким чином конструктор може приймати listабо що завгодно, що веде себе як список, а новий екземпляр веде себе як a list. Дивіться відповідь Кіта нижче, в якій повинен бути прийнятий один ІМХО.
Ерік

1
@Eric Я розбирав питання не як OOP-специфічний, а як "чому списки не мають аналогії, щоб dict.get()повернути значення за замовчуванням з посилання індексу списку замість того, щоб ловити IndexError? Отже, це дійсно стосується функції мови / бібліотеки (а не OOP Більше того, слід, мабуть, кваліфікувати ваше використання "пітонічного", як, можливо, 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") АБО (результат = url.split (); len (результат)> 2 та результат [2] == "lalala"). І так, я знаю, що сам можу написати таку функцію =), але мене здивувало, що така функція не вбудована.
CSZ

1
Я кажу, що у вашому випадку ви робите неправильно. Обробку URL-адрес слід здійснювати або за маршрутами (узгодження шаблону), або шляхом обходу об’єктів. Але, щоб відповісти на ваш конкретний випадок: 'lalala' in url.split('/')[2:]. Але проблема вашого рішення тут полягає в тому, що ви дивитесь лише на другий елемент. Що робити, якщо URL-адреса "/ monkeybonkey / lalala"? Ви отримаєте Trueнезважаючи на те, що URL-адреса недійсна.
Леннарт Регебро

Я взяв лише другий елемент, тому що мені потрібен був лише другий елемент. Але так, шматочки здаються гарною робочою альтернативою
CSZ

@CSZ: Але тоді перший елемент ігнорується, і в цьому випадку ви можете його пропустити. :) Дивіться, що я маю на увазі, приклад не так добре працює в реальному житті.
Леннарт Регебро
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.