Правильный стиль для функций Python, которые мутируют аргумент

Я хотел бы написать функцию на Python, который мутирует один из аргументов (который является списком, т. е. изменяемым). Что-то вроде этого:--3-->

def change(array):
   array.append(4)

change(array)

Я больше знаком с передачей по значению, чем с настройкой Python (как бы вы ни решили ее назвать). Поэтому я обычно пишу такую функцию:

def change(array):
  array.append(4)
  return array

array = change(array)

вот мое замешательство. Поскольку я могу просто мутировать аргумент, второй метод будет казаться излишним. Но первый! .. --7-->неправильно. кроме того, мой конкретная функция будет иметь несколько параметров, только один из которых будет меняться. Второй метод дает понять, какой аргумент изменяется (потому что он присваивается переменной). Первый метод не дает никаких указаний. Существует ли конвенция? Что "лучше"? Спасибо.

3 ответов


первый вариант:

def change(array):
   array.append(4)

change(array)

наиболее идиоматические способ сделать это. Как правило, в python мы ожидаем, что функция либо мутирует аргументы, либо возвращает что-то1. Причина этого в том, что если функция ничего не возвращает, то это делает совершенно ясным, что функция должна иметь некоторый побочный эффект, чтобы оправдать ее существование (например, изменение входных данных).

на оборотной стороне, если вы делаете вещи во-вторых путь:

def change(array):
  array.append(4)
  return array

array = change(array)

вы уязвимы, чтобы иметь трудно отслеживать ошибки, где изменяемый объект меняется внезапно, когда вы не ожидали, что он ... " но я думал change сделал копию"...

1технически каждая функция возвращает значение что-то, что _something_ просто так получилось None ...


соглашение в Python-это функции или мутировать что-то, или вернуть что-то, не так.

если оба полезны, вы обычно пишете две отдельные функции, с мутатором, названным для активного глагола, такого как change, и не-мутатор названный для причастия как changed.

почти все в builtins и stdlib следует этому шаблону. The list.append способ вы звоните возвращает ничего. То же самое с list.sort-но sorted оставляет свой аргумент в покое и вместо этого возвращает новую отсортированную копию.

есть несколько исключений для некоторых специальных методов (например, __iadd__ должен мутировать, а затем вернуться self), и несколько случаев, когда явно должна быть одна вещь, мутирующая и разные вещь возвращается (например,list.pop), и для библиотек, которые пытаются использовать Python как своего рода доменный язык, где согласуется с идиомы целевого домена более важны, чем согласованность с идиомами Python (например, некоторые библиотеки выражений SQL-запросов). Как и все конвенции, эта соблюдается, если нет веской причины не делать этого.


Итак, почему Python был разработан таким образом?

Ну, во-первых, это делает некоторые ошибки очевидными. Если вы ожидали, что функция не мутирует и возвращает значение, будет довольно очевидно, что вы ошиблись, потому что вы получите ошибку, как AttributeError: 'NoneType' object has no attribute 'foo'.

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

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

конечно, все это компромисс-это делает некоторый код более подробным в Python, чем, скажем, в JavaScript,-но это компромисс, который глубоко встроен в дизайн Python.


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

есть третий вариант, который вы не показали в своем вопросе. Вместо того, чтобы мутировать объект, переданный как аргумент, сделайте скопировать этого аргумента и верните его вместо этого. Это делает им чисто функцию без побочных эффектов.

def change(array):
  array_copy = array[:]
  array_copy.append(4)
  return array_copy

array = change(array)