Почему списки Python не создают копии аргументов, поэтому реальные объекты не могут быть видоизменены? - PullRequest
1 голос
/ 22 августа 2010

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

>>> d = [1, 2, 3, 4, 5]
>>> [d.pop() for _ in range(len(d))]
[5, 4, 3, 2, 1]
>>> d
[]

Почему d не копируется, а затем скопированная версия с лексической областью не видоизменяется (а затем теряется)? Точка понимания списка выглядит так: возвращает нужный список, а не возвращает список и незаметно изменяет какой-то другой объект за кулисами. Разрушение d несколько неявно, что кажется пифоническим. Есть хороший пример использования для этого?

Почему выгодно, чтобы списочные операции вели себя как циклы, а не как функции (из функционального языка, с локальной областью видимости)?

Ответы [ 14 ]

21 голосов
/ 22 августа 2010

Python никогда не копирует, если вы специально не попросите его сделать копию. Это совершенно простое, понятное и полностью понятное правило. Наложение на него исключений и различий, таких как «за исключением следующих обстоятельств в пределах понимания списка ...», было бы полной глупостью: если бы дизайн Python когда-либо находился под управлением кого-то с такими сумасшедшими идеями, Python был бы больным искаженный, полуразбитый язык не стоит изучать. Спасибо за то, что снова осчастливили меня осознанием того, что это не случай!

Вы хотите копию? Сделайте копию! Это всегда решение в Python, когда вы предпочитаете накладные расходы на копию, потому что вам нужно внести некоторые изменения, которые не должны отражаться в оригинале. То есть при чистом подходе вы бы сделали

dcopy = list(d)
[dcopy.pop() for _ in range(len(d))]

Если вы очень заинтересованы в том, чтобы все было в одном выражении, вы можете, хотя это, возможно, не код, который можно было бы назвать «чистым»:

[dcopy.pop() for dcopy in [list(d)] for _ in range(len(d))]

т. Е. Обычный трюк, который используется, когда действительно хочется сложить присвоение в понимание списка (добавьте предложение for, где «управляющая переменная» - это имя, которое вы хотите назначить, и «цикл» "находится над последовательностью из одного элемента значения, которое вы хотите присвоить).

Функциональные языки никогда не изменяют данные, поэтому они также не копируют (и не обязаны). Python не является функциональным языком, но, конечно, есть много вещей, которые вы можете делать в Python "функциональным способом", и часто это лучший способ. Например, намного лучшая замена для вашего понимания списка (гарантированно будет иметь идентичные результаты и не повлияет на d, а значительно быстрее, более кратко и чище):

d[::-1]

(АКА "Марсианский смайлик", по моей жене Анне ;-). Срез (не срез присваивание , что является другой операцией) всегда выполняет копирование в ядре Python (язык и стандартная библиотека), хотя, конечно, не обязательно в независимо разработанных сторонних модулях, таких как популярный numpy ( который предпочитает видеть срез как «вид» на оригинале numpy.array).

7 голосов
/ 22 августа 2010

В этом выражении:

[d.pop() for _ in range(len(d))]

какую переменную вы хотите неявно скопировать или определить? Единственная переменная здесь с каким-либо особым статусом в понимании - это _, который не тот, который вы хотите защитить.

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

Вы упоминаете функциональные языки, но они выполняют то, что вы хотите, делая все переменные неизменяемыми. Python просто не спроектирован таким образом.

5 голосов
/ 22 августа 2010

Почему он должен создавать (возможно, очень дорогую) копию, когда идиоматический код все равно не будет иметь побочных эффектов? И почему (редкие, но существующие) следует использовать случаи, когда желательны побочные эффекты ( и ок) быть запрещенным?

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

4 голосов
/ 22 августа 2010

Почему выгодно, чтобы списочные вычисления вели себя точно так же, как для циклов,

Потому что это наименее удивительно.

, а не ведут себя как функции (с локальным охватом)?

О чем ты говоришь?Функции могут видоизменять свои аргументы:

>>> def mutate(d):
...     d.pop()
... 
>>> d = [1, 2, 3, 4, 5]
>>> mutate(d)
>>> d
[1, 2, 3, 4]

Я не вижу никаких противоречий вообще.

То, что вы, кажется, не признаете, это то, что Python не является функциональным языком.Это императивный язык, который имеет несколько функциональных функций.Python позволяет объектам быть изменяемыми.Если вы не хотите, чтобы они были видоизменены, просто не вызывайте методы, подобные list.pop, которые задокументированы, чтобы изменить их.

4 голосов
/ 22 августа 2010

d не копируется, поскольку вы его не копировали, списки изменчивы и pop контрактно манипулирует списком.

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

>>> x = (1, 2, 3, 4)
>>> type(x)
<type 'tuple'>
>>> x.pop()
AttributeError: 'tuple' object has no attribute 'pop'
3 голосов
/ 22 августа 2010

Вы, похоже, неправильно понимаете функции:

def fun(lst):
    for _ in range(len(lst)):
        lst.pop()

будет иметь тот же эффект, что и

(lst.pop() for _ in range(len(lst)))

Это потому, что lst не является списком '', а ссылкой на него,Когда вы передаете эту ссылку, она продолжает указывать на тот же список.Если вы хотите скопировать список, просто используйте lst[:].Если вы также хотите скопировать его содержимое, используйте copy.deepcopy из модуля copy.

2 голосов
/ 21 сентября 2016

Если я подумаю о вашем «понимании списка», оно само по себе является «непифоническим».Вы ссылаетесь с помощью d.pop () на d, и у вас фактически нет ссылки на список в «понимании списка».Таким образом, на самом деле вы неправильно используете понимание списка для простого цикла for, используя фиктивную переменную '_', которую вы не используете для того, что вы фактически делаете или собираете в этом выражении: 'd.pop ()'.Метод pop () применяется к d.И не имеет ничего общего ни с _, ни с range (len (d)) - который создает просто еще один список, используя длину d.Методы в списках изменяют сам список.Так что «логично», что d изменяется при применении этого метода.

Как ответил Алекс Мартелли, d [:: - 1] делает то, что должно делать это выражение «питоническим» образом.

2 голосов
/ 22 августа 2010

Why is d not copied, and then the copied lexically-scoped version not mutated (and then lost)?

Поскольку python является объектно-ориентированным языком программирования, это было бы невероятно плохой идеей. Все является объектом .

Что заставляет вас думать, что можно создавать "лексически скопированные копии" произвольных объектов?

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


Why is it advantageous to have list comps behave exactly like for loops, rather than behave more like functions (with local scope)?

  1. Поскольку он создает краткий, читабельный код.
  2. Как и все остальные отмечали, функции не работают так, как вы, кажется, думаете, что они работают. Они также не занимаются этой «лексической копией». Я думаю, что вы путаетесь с местным заданием.

Рекомендую прочитать статьи здесь: http://www.cafepy.com/article/python_types_and_objects/python_types_and_objects.html

Они очень информативны о том, как работает python.

2 голосов
/ 22 августа 2010

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

c = [a for a in reversed(d)]
c = d[::-1]
c = [d[a] for a in xrange(len(d)-1, -1, -1)]

все даст вам обратную копию list.В то время как

d.reverse()

обратит list на месте.

2 голосов
/ 22 августа 2010

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

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

...