Вопрос-Ответ

Why do these list methods (append, sort, extend, remove, clear, reverse) return None rather than the resulting list?

Почему эти методы списка (append, sort, extend, remove, clear, reverse) возвращают None, а не результирующий список?

Я заметил, что многие операции со списками, которые изменяют содержимое списка, будут возвращать None, а не сам список. Примеры:

>>> mylist = ['a', 'b', 'c']
>>> empty = mylist.clear()
>>> restored = mylist.extend(range(3))
>>> backwards = mylist.reverse()
>>> with_four = mylist.append(4)
>>> in_order = mylist.sort()
>>> without_one = mylist.remove(1)
>>> mylist
[0, 2, 4]
>>> [empty, restored, backwards, with_four, in_order, without_one]
[None, None, None, None, None, None]

Какой мыслительный процесс стоит за этим решением?

Мне это кажется затруднительным, поскольку предотвращает "цепочку" обработки списка (например, mylist.reverse().append('a string')[:someLimit]). Я предполагаю, что, возможно, "Сильные мира сего" решили, что понимание списка - лучшая парадигма (обоснованное мнение), и поэтому не хотели поощрять другие методы - но кажется извращенным препятствовать интуитивному методу, даже если существуют лучшие альтернативы.


Этот вопрос конкретно касается проектного решения Python возвращать None из изменяющихся методов списка , таких как .append. Однако новички часто пишут неверный код, который ожидает, что .append (в частности) вернет тот же список, который был только что изменен. Однако, пожалуйста, закрывайте такие вопросы как дублирующие этот. "Код сделал что-то не так, потому что результатом был None , а не список" - это то, что OP в этих случаях должен был обнаружить независимо посредством отладки; создание правильного MRE оставляет после себя вопрос, подобный этому, - следовательно, его можно считать дубликатом.

Смотрите Как я могу собрать результаты повторного вычисления в список, словарь и т.д. (или сделать копию списка с измененным каждым элементом)? на простой вопрос "как мне добавлять список повторно?" (или вопросы отладки, которые сводятся к этой проблеме). Это новый canonical, который был специально подготовлен для рассмотрения темы с точки зрения, которой не хватает новичкам.

Чтобы получить измененные версии списка, см.:



Та же проблема применима к некоторым методам других встроенных типов данных, например, set.discard (см. Как удалить определенный элемент из наборов внутри списка с помощью понимания списка) и dict.update (см. Почему python dict.update() не возвращает объект?).

Те же рассуждения применимы и к разработке ваших собственных API. Смотрите, Разве выполнение операций на месте возвращает объект плохой идеей?.

Переведено автоматически
Ответ 1

Общий принцип проектирования в Python предназначен для функций, которые изменяют объект на месте для возврата None. Я не уверен, что это был бы тот вариант дизайна, который я бы выбрал, но в основном это делается для того, чтобы подчеркнуть, что новый объект не возвращается.

Гвидо ван Россум (наш Python BDFL) указывает выбор дизайна в списке рассылки Python-Dev:


Я хотел бы еще раз объяснить, почему я так непреклонен в том, что sort() не должен возвращать 'self'.


Это происходит из стиля кодирования (популярного в различных других языках, я полагаю, особенно им пользуется Lisp), где ряд побочных эффектов для одного объекта может быть связан следующим образом:


x.compress().chop(y).sort(z)

что было бы то же самое, что


x.compress()
x.chop(y)
x.sort(z)

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


Я хотел бы зарезервировать цепочку для операций, которые возвращают новые значения, таких как операции обработки строк:


y = x.rstrip("\n").split(":").lower()

Есть несколько стандартных библиотечных модулей, которые поощряют объединение в цепочку вызовов с побочным эффектом
(на ум приходит pstat ). Новых быть не должно
; pstat проскользнул через мой фильтр, когда он был слабым.


Ответ 2

Я не могу говорить за разработчиков, но я нахожу такое поведение очень интуитивно понятным.

Если метод работает с исходным объектом и изменяет его на месте, он ничего не возвращает, потому что в нем нет новой информации - у вас, очевидно, уже есть ссылка на (теперь измененный) объект, так зачем возвращать его снова?

Однако, если метод или функция создает новый объект, то, конечно, они должны его вернуть.

Таким образом, l.reverse() ничего не возвращает (потому что теперь список был перевернут, но identfier l все еще указывает на этот список), но reversed(l) должен возвращать вновь созданный список, потому что l все еще указывает на старый, неизмененный список.

РЕДАКТИРОВАТЬ: Я только что узнал из другого ответа, что этот принцип называется разделением команд-запросов.

Ответ 3

Можно возразить, что сама сигнатура дает понять, что функция изменяет список, а не возвращает новый: если бы функция возвращала список, ее поведение было бы гораздо менее очевидным.

Ответ 4

Если вас отправили сюда после просьбы о помощи в исправлении вашего кода:

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

Если у вас был код, вызывающий метод типа .append или .sort в списке, вы заметите, что возвращаемое значение равно None, в то время как список изменен на месте. Внимательно изучите пример:

>>> x = ['e', 'x', 'a', 'm', 'p', 'l', 'e']
>>> y = x.sort()
>>> print(y)
None
>>> print(x)
['a', 'e', 'e', 'l', 'm', 'p', 'x']

y получено специальное None значение, потому что именно оно было возвращено. x изменено, потому что сортировка произошла на месте.

Это работает таким образом специально, чтобы код типа x.sort().reverse() прерывался. Посмотрите другие ответы, чтобы понять, почему разработчики Python хотели именно этого.

To fix the problem

First, think carefully about the intent of the code. Should x change? Do we actually need a separate y?

Let's consider .sort first. If x should change, then call x.sort() by itself, without assigning the result anywhere.

If a sorted copy is needed instead, use y = x.sorted(). See How can I get a sorted copy of a list? for details.

For other methods, we can get modified copies like so:

.clear -> there is no point to this; a "cleared copy" of the list is just an empty list. Just use y = [].

.append and .extend -> probably the simplest way is to use the + operator. To add multiple elements from a list l, use y = x + l rather than .extend. To add a single element e wrap it in a list first: y = x + [e]. Another way in 3.5 and up is to use unpacking: y = [*x, *l] for .extend, y = [*x, e] for .append. See also How to allow list append() method to return the new list for .append and How do I concatenate two lists in Python? for .extend.

.reverse -> First, consider whether an actual copy is needed. The built-in reversed gives you an iterator that can be used to loop over the elements in reverse order. To make an actual copy, simply pass that iterator to list: y = list(reversed(x)). See How can I get a reversed copy of a list (avoid a separate statement when chaining a method after .reverse)? for details.

.remove -> Figure out the index of the element that will be removed (using .index), then use slicing to find the elements before and after that point and put them together. As a function:

def without(a_list, value):
index = a_list.index(value)
return a_list[:index] + a_list[index+1:]

(We can translate .pop similarly to make a modified copy, though of course .pop actually returns an element from the list.)

See also A quick way to return list without a specific element in Python.

(If you plan to remove multiple elements, strongly consider using a list comprehension (or filter) instead. It will be much simpler than any of the workarounds needed for removing items from the list while iterating over it. This way also naturally gives a modified copy.)


For any of the above, of course, we can also make a modified copy by explicitly making a copy and then using the in-place method on the copy. The most elegant approach will depend on the context and on personal taste.

python list