Python идиома, чтобы вернуть первый элемент или нет
Я уверен, что есть более простой способ сделать это, что просто не приходит мне в голову.
Я вызываю кучу методов, которые возвращают список. Список может быть пустым. Если список непустой, я хочу вернуть первый элемент; в противном случае я хочу вернуть None. Этот код работает:
my_list = get_list()
if len(my_list) > 0: return my_list[0]
return None
Edit:
причина, по которой я ищу однострочное выражение здесь, заключается не в том, что мне нравится невероятно краткий код, а потому, что мне приходится писать много такого кода:
x = get_first_list()
if x:
# do something with x[0]
# inevitably forget the [0] part, and have a bug to fix
y = get_second_list()
if y:
# do something with y[0]
# inevitably forget the [0] part AGAIN, and have another bug to fix
то, что я хотел бы делать, безусловно, может быть сделано с помощью функции (и, вероятно, будет):
def first_item(list_or_none):
if list_or_none: return list_or_none[0]
x = first_item(get_first_list())
if x:
# do something with x
y = first_item(get_second_list())
if y:
# do something with y
Я отправил вопрос, потому что я часто удивляюсь тому, что простые выражения в Python могут делать, и я думал, что написание функции было глупой вещью, если бы было простое выражение, которое могло бы сделать трюк. Но видя эти ответы, кажется, что функция и простое решение.
23 ответов:
Python 2.6+
next(iter(your_list), None)если
your_listможет бытьNone:next(iter(your_list or []), None)Python 2.4
def get_first(iterable, default=None): if iterable: for item in iterable: return item return defaultпример:
x = get_first(get_first_list()) if x: ... y = get_first(get_second_list()) if y: ...другой вариант-встроить вышеуказанную функцию:
for x in get_first_list() or []: # process x break # process at most one item for y in get_second_list() or []: # process y breakизбежать
breakвы могли бы написать:for x in yield_first(get_first_list()): x # process x for y in yield_first(get_second_list()): y # process yгде:
def yield_first(iterable): for item in iterable or []: yield item return
лучший способ это:
a = get_list() return a[0] if a else Noneвы также можете сделать это в одной строке, но программисту гораздо сложнее читать:
return (get_list()[:1] or [None])[0]
(get_list() or [None])[0]Это должно сработать.
кстати я не использовал переменную
list, потому что это перезаписывает встроенный
самый идиоматический способ python-использовать следующий () на итераторе, так как список iterable. так же, как то, что @J. F. Sebastian поставил в комментарии 13 декабря 2011 года.
next(iter(the_list), None)Это возвращает None ifthe_listпусто. смотрите next () Python 2.6+или если вы знаете точно
the_listне пуст:
iter(the_list).next()посмотреть итератор.следующий() в Python 2.2+
ФП почти нет, есть только несколько вещей, чтобы сделать его более подходящие для Python.
во-первых, нет необходимости, чтобы получить длину списка. Пустые списки в Python оцениваются как False в проверке if. Просто скажите
if list:кроме того, это очень плохая идея, чтобы назначить переменные, которые совпадают с зарезервированными словами. "список" - это зарезервированное слово в Python.
так Давайте изменим это на
some_list = get_list() if some_list:очень важный момент, который много решений здесь Мисс это все функции/методы Python возвращают None по умолчанию. Попробуйте следующее ниже.
def does_nothing(): pass foo = does_nothing() print fooЕсли вам не нужно возвращать None для досрочного завершения функции, нет необходимости явно возвращать None. Довольно лаконично, просто верните первую запись, если она существует.
some_list = get_list() if some_list: return list[0]и, наконец, возможно, это подразумевалось, но только чтобы быть явным (потому что явное лучше, чем неявное), вы не должны есть функция получения списка из другой функции, просто передайте его в качестве параметра. Таким образом, конечный результат будет
def get_first_item(some_list): if some_list: return list[0] my_list = get_list() first_item = get_first_item(my_list)как я уже сказал, OP был почти там, и всего несколько прикосновений дают ему аромат питона, который вы ищете.
Если вы обнаружите, что пытаетесь вырвать первую вещь (или нет) из понимания списка, вы можете переключиться на генератор, чтобы сделать это так:
next((x for x in blah if cond), None)Pro: работает, если blah не индексируется Con: это незнакомый синтаксис. Это полезно при взломе и фильтрации материала в ipython, хотя.
честно говоря, я не думаю, что есть лучше идиома: ваша ясна и лаконична - не нужно ничего "лучше". Может быть, но это действительно дело вкуса, вы могли бы изменить
if len(list) > 0:сif list:- пустой список всегда будет иметь значение False.в соответствующей заметке Python-это не Perl (не каламбур!), вам не нужно получать самый крутой код.
На самом деле, худший код, который я видел в Python, тоже был очень крутым :-) и полностью unmaintainable.кстати, большинство решений, которые я видел здесь, не учитывают, когда list[0] оценивается как False (например, пустая строка или ноль) - в этом случае все они возвращают None и не правильный элемент.
Так, вот еще одна возможность.
return None if not get_list() else get_list()[0]выгода: Этот метод обрабатывает случай, когда get_list нет, без использования try/except или присваивания. Насколько мне известно, ни одна из реализаций выше не может справиться с этой возможностью
падений: get_list () вызывается дважды, совершенно излишне, особенно если список длинный и/или создается при вызове функции.
правда, это более "подходящие для Python" по-моему, предоставьте код, который читается, чем он должен сделать однострочный только потому, что вы можете :) я должен признать, что я виноват много раз в излишнем сжатии кода Python только потому, что я так впечатлен тем, как мало я могу сделать сложную функцию выглядеть :)
изменить: Как пользователь" hasen j " прокомментировал ниже, условное выражение выше является новым в Python 2.5, как описано здесь:https://docs.python.org/whatsnew/2.5.html#pep-308. Спасибо, Хасен!
в Python идиома, чтобы вернуть первый элемент или нет?
def get_first(l): return l[0] if l else Noneи если список возвращается из
get_listфункция:l = get_list() return l[0] if l else Noneдругие способы, продемонстрированные для этого здесь, с объяснения
forкогда я начал думать об умных способах сделать это, Это вторая вещь, о которой я подумал:
for item in get_list(): return itemэто предполагает, что функция заканчивается здесь, неявно возвращается
Noneеслиget_listвозвращает пустой список. Приведенный ниже явный код в точности эквивалентен:for item in get_list(): return item return None
if some_listтакже было предложено следующее (я исправил неверное имя переменной), которое также использует неявное
None. Это было бы предпочтительнее, чем выше, так как он использует логическую проверку вместо итерации, которая может не произойти. Это должно быть легче понять сразу, что происходит. Но если мы пишем для удобочитаемости и ремонтопригодности, мы также должны добавить явныйreturn Noneв конце:some_list = get_list() if some_list: return some_list[0]slice
or [None]и выберите нулевой индексЭтот также находится в самом популярном ответе:
return (get_list()[:1] or [None])[0]ломтик не нужен, и создает дополнительный список из одного элемента в памяти. Следующее должно быть более эффективным. Чтобы объяснить,
orвозвращает второй элемент, если первыйFalseв логическом контексте, так что еслиget_listвозвращает пустой список, выражение, содержащееся в скобках, вернет список с "None", к которому затем будет обращаться :return (get_list() or [None])[0]следующий использует тот факт, что и возвращает второй пункт, если первый
Trueв логическом контексте, и так как это ссылается my_list дважды, это не лучше, чем тернарное выражение (и технически не однострочное):my_list = get_list() return (my_list and my_list[0]) or None
nextтогда у нас есть следующее умное использование встроенного
nextиiterreturn next(iter(get_list()), None)объяснить,
iterвозвращает итератор с.nextметод. (.__next__в Python 3.) Тогда встроенныйnextзвонки.nextметод, и если итератор исчерпан, возвращает значение по умолчанию, которое мы даем,None.избыточное тернарное выражение (
a if b else c) и возвращаетсяниже было предложено, но обратное было бы предпочтительнее, так как логика обычно лучше понимается в положительном, а не в отрицательном. Так как
get_listвызывается дважды, если результат не запомнится каким-то образом, это будет работать плохо:return None if not get_list() else get_list()[0]лучше обратная:
return get_list()[0] if get_list() else Noneеще лучше, используйте локальную переменную, чтобы
get_listвызывается только один раз, и у вас есть рекомендуемое решение Pythonic, которое сначала обсуждалось:l = get_list() return l[0] if l else None
из любопытства я запустил тайминги для двух решений. Решение, которое использует оператор return для преждевременного завершения цикла for, немного дороже на моей машине с Python 2.5.1, я подозреваю, что это связано с настройкой iterable.
import random import timeit def index_first_item(some_list): if some_list: return some_list[0] def return_first_item(some_list): for item in some_list: return item empty_lists = [] for i in range(10000): empty_lists.append([]) assert empty_lists[0] is not empty_lists[1] full_lists = [] for i in range(10000): full_lists.append(list([random.random() for i in range(10)])) mixed_lists = empty_lists[:50000] + full_lists[:50000] random.shuffle(mixed_lists) if __name__ == '__main__': ENV = 'import firstitem' test_data = ('empty_lists', 'full_lists', 'mixed_lists') funcs = ('index_first_item', 'return_first_item') for data in test_data: print "%s:" % data for func in funcs: t = timeit.Timer('firstitem.%s(firstitem.%s)' % ( func, data), ENV) times = t.repeat() avg_time = sum(times) / len(times) print " %s:" % func for time in times: print " %f seconds" % time print " %f seconds avg." % avg_timeвот тайминги, которые я получил:
empty_lists: index_first_item: 0.748353 seconds 0.741086 seconds 0.741191 seconds 0.743543 seconds avg. return_first_item: 0.785511 seconds 0.822178 seconds 0.782846 seconds 0.796845 seconds avg. full_lists: index_first_item: 0.762618 seconds 0.788040 seconds 0.786849 seconds 0.779169 seconds avg. return_first_item: 0.802735 seconds 0.878706 seconds 0.808781 seconds 0.830074 seconds avg. mixed_lists: index_first_item: 0.791129 seconds 0.743526 seconds 0.744441 seconds 0.759699 seconds avg. return_first_item: 0.784801 seconds 0.785146 seconds 0.840193 seconds 0.803380 seconds avg.
что касается идиом, есть рецепт модуле itertools под названием
nth.из рецептов itertools:
def nth(iterable, n, default=None): "Returns the nth item or a default value" return next(islice(iterable, n, None), default)если вы хотите однострочные, подумайте об установке библиотеки, которая реализует этот рецепт для вас, например
more_itertools:import more_itertools as mit mit.nth([3, 2, 1], 0) # 3 mit.nth([], 0) # default is `None` # Noneдоступен другой инструмент, который возвращает только первый элемент, называемый
more_itertools.first.mit.first([3, 2, 1]) # 3 mit.first([], default=None) # Noneэти itertools масштабируются в общем случае для любого iterable, а не только для списки.
def head(iterable): try: return iter(iterable).next() except StopIteration: return None print head(xrange(42, 1000) # 42 print head([]) # Noneкстати: я бы переработал ваш общий поток программы в нечто вроде этого:
lists = [ ["first", "list"], ["second", "list"], ["third", "list"] ] def do_something(element): if not element: return else: # do something pass for li in lists: do_something(head(li))(избегая повторения, когда это возможно)
Как насчет этого:
(my_list and my_list[0]) or NoneПримечание: это должно работать нормально для списков объектов, но это может вернуть неправильный ответ в случае числа или списка строк в комментариях ниже.
вы могли бы использовать Извлечение Метода. Другими словами, извлеките этот код в метод, который вы затем вызовете.
Я бы не стал пытаться сжать его намного больше, одни лайнеры кажутся более трудными для чтения, чем многословная версия. И если вы используете метод извлечения, это один лайнер;)
а как же:
next(iter(get_list()), None)? Возможно, не самый быстрый, но стандартный (начиная с Python 2.6) и емким.
наверное, не самое быстрое решение, но никто не упомянул такой вариант:
dict(enumerate(get_list())).get(0)если
get_list()может возвратитьNoneвы можете использовать:dict(enumerate(get_list() or [])).get(0)плюсы:
-одна строка
-вы просто называете
get_list()после-легко понять
мой вариант использования был только для установки значения локальной переменной.
лично я нашел попробовать и кроме стиля чище читать
items = [10, 20] try: first_item = items[0] except IndexError: first_item = None print first_itemчем нарезка списка.
items = [10, 20] first_item = (items[:1] or [None, ])[0] print first_item
несколько человек предложили сделать что-то вроде этого:
list = get_list() return list and list[0] or Noneэто работает во многих случаях, но это будет работать только в том случае, если list[0] не равен 0, False или пустой строке. Если list[0] равно 0, False или пустой строке, метод неверно вернет None.
Я создал эту ошибку в моем собственном коде слишком много раз !
не идиоматические питона эквивалент в C-стиле тернарные операторы
cond and true_expr or false_exprie.
list = get_list() return list and list[0] or None
Comments