Польза-дель-плохо?
Я обычно использую del
в своем коде для удаления объектов:
>>> array = [4, 6, 7, 'hello', 8]
>>> del(array[array.index('hello')])
>>> array
[4, 6, 7, 8]
>>>
, но я слышал многие люди говорят, что использование del
- это unpythonic. Является ли использование del
плохой практикой?
>>> array = [4, 6, 7, 'hello', 8]
>>> array[array.index('hello'):array.index('hello')+1] = ''
>>> array
[4, 6, 7, 8]
>>>
Если нет, то почему существует много способов сделать то же самое в python? Разве один лучше других?
Вариант 1: Использование del
>>> arr = [5, 7, 2, 3]
>>> del(arr[1])
>>> arr
[5, 2, 3]
>>>
Вариант 2: Использование list.remove()
>>> arr = [5, 7, 2, 3]
>>> arr.remove(7)
>>> arr
[5, 2, 3]
>>>
Вариант 3: Использование list.pop()
>>> arr = [5, 7, 2, 3]
>>> arr.pop(1)
7
>>> arr
[5, 2, 3]
>>>
Вариант 4: Использование нарезки
>>> arr = [5, 7, 2, 3]
>>> arr[1:2] = ''
>>> arr
[5, 2, 3]
>>>
Мне жаль, если этот вопрос кажется основанным на мнении, но я ищу разумный ответ на свой вопрос, и я добавлю вознаграждение через 2 дня, если я не получу подходящего ответа.
Правка:
Поскольку существует много альтернатив использованиюdel
для удаления определенных частей объектов, единственным уникальным фактором, оставшимся от del
, является его способность полностью удалять объекты:
>>> a = 'hello'
>>> b = a
>>> del(a)
>>> a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> b
'hello'
>>>
Однако, какой смысл использовать его для "определения" объектов?
Также, почему следующий код изменяет обе переменные:
>>> a = []
>>> b = a
>>> a.append(9)
>>> a
[9]
>>> b
[9]
>>>
Но утверждение del
не дает того же эффекта?
>>> a = []
>>> b = a
>>> del(a)
>>> a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
>>> b
[]
>>>
7 ответов:
Другие ответы рассматривают его с технической точки зрения (например, как лучше всего изменить список), но я бы сказал, что (гораздо) более важная причина, по которой люди предлагают, например, нарезать, заключается в том, что он не изменяет исходный список.
Причина этого, в свою очередь, заключается в том, что обычно список приходил откуда-то. Если вы измените его, вы можете неосознанно вызвать очень плохие и труднодоступные побочные эффекты, которые могут вызвать ошибки в других частях программы. Или даже если вы не вызываете ошибка немедленно, вы сделаете вашу программу в целом труднее понять и рассуждать о, и отлаживать.Например, выражения понимания списка / генератора хороши тем, что они никогда не изменяют" исходный " список, который они передают:
Это, конечно, часто дороже (с точки зрения памяти), потому что он создает новый список, но программа, которая использует этот подход, математически чище и легче рассуждать. А с ленивыми списками (генераторами и генераторными выражениями) даже накладные расходы на память исчезнут, и вычисления будут выполняться только по требованию; см. http://www.dabeaz.com/generators/ за потрясающее вступление. И вы не должны слишком много думать об оптимизации при разработке своей программы (см. https://softwareengineering.stackexchange.com/questions/80084/is-premature-optimization-really-the-root-of-all-evil)кроме того, удаление элемента из списка довольно дорого, если только это не связанный список (чего нет в Python[x for x in lst if x != "foo"] # creates a new list (x for x in lst if x != "foo") # creates a lazy filtered stream
list
; for linked список, см.collections.deque
).
Фактически, свободные от побочных эффектов функции и неизменяемые структуры данных являются основой функционального программирования, очень мощной парадигмы программирования.
Однако при определенных обстоятельствах можно изменить структуру данных на месте (даже в FP, , Если язык позволяет это ), например, когда она локально создана или скопирована из входных данных функции:
def sorted(lst): ret = list(lst) # make a copy # mutate ret return ret
- эта функция, по-видимому, является чистая функция извне, потому что она не изменяет свои входные данные (а также зависит только от своих аргументов и ничего больше (т. е. у нее нет (глобального) состояния), что является еще одним требованием для того, чтобы что-то было чистым функция ).
Так что, пока вы знаете, что делаете,del
ни в коем случае не плохо; но используйте любой вид мутации данных с крайней осторожностью и только тогда, когда это необходимо. Всегда начинайте с возможно менее эффективного, но более правильного и математически элегантный код....и научиться функциональному программированию :)
P.S. обратите внимание, что
del
также может использоваться для удаления локальных переменных и, таким образом, устранения ссылок на объекты в памяти, что часто полезно для любых целей, связанных с GC.
Ответ на ваш второй вопрос:
Что касается второй части вашего вопроса о
del
удаление объектов полностью - это не так: на самом деле в Python это не так. даже можно сказать интерпретатору / виртуальной машине, чтобы удалить объект из памяти, потому что Python-это язык, собирающий мусор (например, Java, C#, Ruby, Haskell и т. д.), И это среда выполнения, которая решает, что удалить и когда.Вместо этого, что
del
делает при вызове переменной (в отличие от ключа словаря или элемента списка), как это:del a
Заключается в том, что он только удаляет локальную (или глобальную) переменную и Не то, на что указывает переменная (каждая переменная в Python содержит указатель / ссылку на его содержимое, а не само содержимое). На самом деле, поскольку локальные и глобальные значения хранятся как словарь Под капотом (см.
locals()
и ещеglobals()
),del a
эквивалентно:del locals()['a']
Или
del globals()['a']
применительно к глобальному.Итак, если у вас есть:
a = [] b = a
Вы создаете список, храните ссылку на него в
a
, а затем делаете еще одну копию этой ссылки и сохраняете ее вb
, не копируя / не касаясь объекта списка сам. Следовательно, эти два вызова влияют на один и тот же объект:В то время как удалениеa.append(1) b.append(2) # the list will be [1, 2]
b
никоим образом не связано с касанием того, на что указываетb
:a = [] b = a del b # a is still untouched and points to a list
Кроме того, даже когда вы вызываете
P.S. Как указал Свен Маркнах,del
для атрибута объекта (например,del self.a
), Вы все равно фактически изменяете словарьself.__dict__
точно так же, как вы на самом деле изменяетеlocals()
/globals()
когда вы это сделаетеdel a
.del locals()['a']
на самом деле не удаляет локальную переменнуюa
, когда внутри функции, которая является правильной. Вероятно, это связано с тем, чтоlocals()
возвращает копию реальных местных жителей. Тем не менее, ответ остается в целом верным.
Python просто содержит множество различных способов удаления элементов из списка. Все они полезны в различных ситуациях.
Таким образом, все они имеют свое место. Очевидно, что при желании удалить число 8, Пример № 2 является лучшим вариантом, чем 1 или 3. Так что это действительно то, что имеет смысл в зависимости от обстоятельств и что наиболее логично.# removes the first index of a list del arr[0] # Removes the first element containing integer 8 from a list arr.remove(8) # removes index 3 and returns the previous value at index 3 arr.pop(3) # removes indexes 2 to 10 del arr[2:10]
EDIT
Разница между Арр.pop(3) и del arr[3] - это то, что pop возвращает удаление элементов. Таким образом, он может быть полезен для переноса удаленных элементов в другие массивы или структуры данных. В остальном они не отличаются друг от друга в использовании.
Нет, я не думаю, что использование
del
вообще плохо. На самом деле, есть ситуации, когда это, по сути, единственный разумный вариант, например, удаление элементов из словаря:Возможно, проблема в том, что новички не до конца понимают, как переменные работают в Python, поэтому использование (или неправильное использование)k = {'foo': 1, 'bar': 2} del k['foo']
del
может быть незнакомым.
Использование
del
само по себе неплохо; однако оно имеет два аспекта, которые способствуют определенным запахам кода:Это побочный эффект, часть последовательности шагов, и сам по себе он не имеет смысла.
Но это вряд ли канон – если бы ключевое слово- возможно, что
del
встречается в коде, который имеет ручное управление памятью, что свидетельствует о плохом понимании области Python и автоматического управления памятью. Точно так же, как операторwith
более идиоматичен для обработки дескрипторов файлов, чемfile.close
, используя scope и контекст более идиоматичен, чем ручная атомизация членов.del
было действительно "плохим", его не было бы в ядре языка. Я просто пытаюсь играть роль адвоката дьявола-объяснить, почему некоторые программисты могут называть его "плохим" и, возможно, дать вам позицию, с которой можно спорить. ;)
Я не думаю, что когда-либо слышал, чтобы кто-то говорил, что
del
является злом, по крайней мере, не больше, чем любая другая языковая особенность. Вопрос междуdel
и другими подходами действительно сводится к вашим прецедентам. Следующие случаи отлично подходят дляdel
:
Удаление переменных из текущей области видимости. Зачем вы хотите это сделать? Представьте, что вы объявляете модуль, который вычисляет переменную пакета, но что потребители этого модуля никогда не нуждаются в нем. В то время как вы могли бы создать целый новый модуль для него, который может быть излишним или может скрыть то, что на самом деле вычисляется. Например, вам может понадобиться следующее:
GLOBAL_1 = 'Some arbitrary thing' GLOBAL_2 = 'Something else' def myGlobal3CalculationFunction(str1, str2): # Do some transforms that consumers of this module don't need return val GLOBAL_3 = myGlobal3CalculationFunction(GLOBAL_1, GLOBAL_2) # Mystery function exits stage left del myGlobal3CalculationFunction
В принципе никто не возражает против использования
del
для удаления переменных из области видимости, когда это необходимо. То же самое относится к значениям в словарях или почти ко всему, что доступно по имени или подобным неизменяемым ссылкам (свойства класса, свойства экземпляра, значения dict и т. д.).Другой случай, когда вы хотите, чтобы удаление элемента из списка или аналогичной упорядоченной последовательности. Которые действительно не сильно отличаются от первого случая в некоторых отношениях (учитывая, что все они могут быть доступны как контейнеры ключ-значение, а списки просто случайно имеют надежно упорядоченные целочисленные ключи). Во всех этих случаях вы находитесь в одной лодке, желая удалить ссылку на некоторые данные, которые существуют в этом конкретном экземпляре (поскольку даже классы являются экземпляром класса). Вы делаете модификацию на месте.
Означает ли наличие упорядоченных и специальных индексов что-то другое для списков? Принципиальное отличие от списка состоит в том, что внесение изменений на месте делает все ваши старые ключи практически бесполезными, если вы не будете очень осторожны. Python дает вам отличную возможность представлять данные очень семантически: вместо того, чтобы иметь список
[actor, verb, object]
и индексы отображения, вы можете иметь хороший дикт{'actor' : actor, 'verb' : verb, 'object' : object}
. В таком доступе часто есть большая ценность (именно поэтому мы получаем доступ функции по имени, а не по номеру): если порядок не важен, зачем делать его жестким? Если ваш заказ важен, почему вы что-то путаете, все ваши ссылки на него недействительны (например, позиции элементов, расстояние между элементами).Вопрос сводится к тому, почему вы будете непосредственно удалять значение списка по индексу. В большинстве случаев операции, которые изменяют отдельные элементы списков на месте, имеют очевидные реализации через другие функции. Убийство предмет с заданным значением? Вы
После того, как вы избавитесь от всех этих случаев, вы получите около двух распространенных вариантов использования для использованияremove
это. Реализация очереди или стека? Выpop
это (не запирайте его). Уменьшение количества ссылок для экземпляра в списке?l[i] = None
работает так же хорошо, и ваши старые индексы все еще указывают на те же самые вещи. Фильтрующие элементы? Выfilter
или используете понимание списка. Сделать копию списка, за вычетом некоторых элементов? Выslice
это. Избавляетесь от дубликатов, хэшируемых элементов? Вы можетеlist(set([]))
или посмотреть наitertools
, Если вам просто нужно пересечь уникальные элементы однажды.del
для списка. Во-первых, вы можете удалять случайные элементы по индексу. Есть более чем несколько случаев, когда это может быть полезно, иdel
абсолютно уместно. Во-вторых, у вас есть сохраненные индексы, которые представляют, где вы находитесь в списке (например, ходите из комнаты в комнату в коридоре, где вы иногда случайно разрушаете комнату, из руководства по программированию Чарли Шина). Этот становится трудно, если у вас есть более одного индекса для одного и того же списка, так как использованиеdel
означает, что все индексы должны быть скорректированы соответствующим образом. Это менее распространено, так как структуры, которые вы проходите, используя индексы, часто не являются теми, из которых вы удаляете элементы (например, координатные сетки для игровой доски). Однако это происходит, например, при циклическом просмотре списка для опроса заданий и удалении завершенных заданий.Это указывает на фундаментальную проблему с удалением элементов из списка на месте по индексу: вы в значительной степени застряли, делая это по одному. Если у вас есть индексы двух элементов для удаления, то удалите первый из них? Есть хороший шанс, что ваш старый индекс не указывает на то, что он раньше. Списки предназначены для хранения порядка. Поскольку
del
изменяет абсолютный порядок, вы застряли, идя или прыгая по списку. Опять же, есть твердые случаи использования (например, случайное разрушение), но есть тонны других случаев, которые просто неверны. Особенно среди новых программистов Python, люди делают ужасные вещи сwhile
циклами на функциях (То есть, петлей, пока вы не найдете значение, которое соответствует входному,del
индексу).Del
требует индекс в качестве входных данных, и как только он запускается, все существующие индексы, ссылающиеся на этот список, ссылаются на совершенно другие данные. Вы можете увидеть, где это кошмар обслуживания, если поддерживается несколько индексов. Опять же, это неплохо. Просто на практике это редко бывает лучшим способом сделать что-то со списком в Python.
Относительно questoin в "Редактировать",
>>> a = [] >>> b = a >>> a.append(9) >>> a [9] >>> b [9] >>> del a >>> a Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'a' is not defined >>> b [9] >>>
Это легко объяснить, помните, что:
>>> id(a) == id(b) True
(
a
иb
указывают на тот же объект в памяти) и что память в python управляется GC. При вызовеdel
объекта вы просто уменьшаете его количество ссылок на 1 (вместе с удалением имени из области видимости), объект уничтожается, когда количество ссылок достигает 0. В этом случаеb
все еще содержит ссылку на объект, поэтому он не уничтожен и все еще доступен.Вы можете найти дополнительную информацию здесь
del
просто мутирует переменная, что иногда бывает лишним. Поэтому, ваши вышеуказанные решения могут быть лучше. Однакоdel
- это единственный способ "уничтожить" переменные и удалить их навсегда:>>> a = 9 >>> del(a) >>> a Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'a' is not defined >>>
Кроме того, вы можете удалить элементы из словарей:
>>> dict = {1: 6} >>> dict[1] 6 >>> del(dict[1]) >>> dict {} >>>