Является ли использование del плохим?

Я обычно использую del в своем коде для удаления объектов:

>>> array = [4, 6, 7, 'hello', 8]
>>> del(array[array.index('hello')])
>>> array
[4, 6, 7, 8]
>>> 

Но я слышал многие люди сказать, что использование del не является питоническим. Является ли использование 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
[]
>>> 

person A.J. Uppal    schedule 28.04.2014    source источник
comment
многие люди [необходима цитата]. Кто это сказал и в каком контексте это было сказано?   -  person Greg Hewgill    schedule 28.04.2014
comment
array.remove('hello') проще, чем извлекать индекс из значения, а затем удалять элемент в индексе. Другая причина, по которой люди могут предпочесть array.remove() вместо del(array...), заключается в том, что array.remove() более объектно-ориентирован.   -  person wookie919    schedule 28.04.2014
comment
Дело в том, что все эти операции модифицируют входной список и являются линейными по времени. Это несколько пустая трата, потому что вы можете создать новый список с удаленным элементом за линейное время, сохранив при этом старый список.   -  person Niklas B.    schedule 28.04.2014
comment
Кстати, списки Python - это списки, а не массивы; в python также есть массивы, поэтому лучше не называть массив переменных вашего списка.   -  person Erik Kaplun    schedule 28.04.2014
comment
использование del, вероятно, неплохо, но наличие синтаксиса del для удаления списковых индексов является неудобной особенностью языка, как и len(..), оба из которых раздражают новичков, которые жалуются на то, что он идет против зерно других популярных языков без четкого обоснования.   -  person Evgeni Sergeev    schedule 28.04.2014
comment
оптимизация всегда бессмысленна в Python, вы все равно сильно отстаете от того, что можете сделать в других языках   -  person Display Name    schedule 28.04.2014
comment
@SargeBorsch Итак, если у кого-то есть тройной вложенный цикл, который можно выполнить за один шаг, мы должны просто сказать им переключиться на C, верно?   -  person Two-Bit Alchemist    schedule 28.04.2014
comment
@EvgeniSergeev Обоснованием часто является полиморфизм. Например, для len зачем иметь list.length, str.length, array.length и т. д., если у вас может быть одна функция, которая даст вам длину любого объекта, который может иметь ее? (т. е. любой объект с __len__)?   -  person Two-Bit Alchemist    schedule 28.04.2014
comment
возможный дубликат Когда del полезен в python?   -  person Wooble    schedule 28.04.2014
comment
@Two-BitAlchemist В языке с утиным типом мы получаем полиморфизм, просто имея методы, которые делают одно и то же, с одним и тем же именем. Хороший вариант использования для len, о котором кто-то упомянул, — это map(len, listoflists), поэтому он имеет свое место. Я не понимаю, почему у нас не должно быть метода .size() и для встроенных структур данных. Встроенный len(..) выполняет небольшую проверку после вызова .__len__() объекта, но он кажется скорее параноидальным, чем полезным stackoverflow.com/questions/496009/   -  person Evgeni Sergeev    schedule 29.04.2014
comment
@EvgeniSergeev len занимается Python дольше, чем это было разумно возможно реализовать. Я предполагаю, что это можно было бы сделать в Py3k, но Python не имеет смешного количества глобальных встроенных модулей, таких как len, и их нужно урезать.   -  person Two-Bit Alchemist    schedule 29.04.2014
comment
@Two-BitAlchemist Я не говорю, что это должно быть устаревшим. Я говорю, что .__len__() не должно быть магией. (Он должен называться .size(), а len(x) вызовет x.size(), или вы можете просто использовать x.size().)   -  person Evgeni Sergeev    schedule 29.04.2014
comment
на самом деле функции могут быть лучше, чем методы — посмотрите на Haskell :)   -  person Erik Kaplun    schedule 30.04.2014
comment
@aj8uppal: вы должны открыть еще один вопрос о самой последней части этого вопроса о del и append.   -  person Erik Kaplun    schedule 30.04.2014
comment
@ErikAllik, хорошо, готово. :)   -  person A.J. Uppal    schedule 30.04.2014
comment
Downvoter, почему минус?   -  person A.J. Uppal    schedule 02.05.2014
comment
@ aj8uppal, не могли бы вы поделиться ссылкой на вопрос о поведении del и =?   -  person pablete    schedule 03.05.2014
comment
Здесь: stackoverflow.com/questions /23377478/   -  person A.J. Uppal    schedule 03.05.2014
comment
вдруг что-то пропало из моего ответа? :) просто любопытно.   -  person Erik Kaplun    schedule 15.05.2014
comment
Извините, я не знаю, почему это произошло :)   -  person A.J. Uppal    schedule 15.05.2014


Ответы (7)


Другие ответы рассматривают это с технической точки зрения (например, как лучше всего изменить список), но я бы сказал, что (намного) более важная причина, по которой люди рекомендуют, например, нарезку, заключается в том, что это не изменить исходный список.

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

Например, выражения списков/генераторов хороши тем, что они никогда не мутируют исходный список, который они передают:

[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

Это, конечно, часто более затратно (с точки зрения памяти), потому что создает новый список, но программа, использующая этот подход, математически чище и проще для понимания. А с ленивыми списками (генераторами и генераторными выражениями) исчезнут даже накладные расходы памяти, а вычисления выполняются только по требованию; см. http://www.dabeaz.com/generators/ для отличного введения. И вы не должны слишком много думать об оптимизации при разработке своей программы (см. evil">https://softwareengineering.stackexchange.com/questions/80084/is-premature-optimization-really-the-root-of-all-evil). Кроме того, удаление элемента из списка довольно затратно, если только это не связанный список (чем не является Python list; для связанного списка см. collections.deque).


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

Тем не менее, при определенных обстоятельствах допустимо изменять существующую структуру данных (даже в FP, если язык позволяет это), например, если он создан локально или скопирован из ввода функции:

def sorted(lst):
    ret = list(lst)  # make a copy
    # mutate ret
    return ret

— эта функция кажется чистой функцией извне, потому что она не изменяет свои входные данные (а также зависит только от своих аргументов и ни от чего другого (т. е. у нее нет (глобального) состояния), что является еще одним требованием для того, чтобы что-то было a Pure Функция).

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

...и изучите функциональное программирование :)

P.S. обратите внимание, что del также можно использовать для удаления локальных переменных и, таким образом, устранения ссылок на объекты в памяти, что часто полезно для любых целей, связанных с сборщиком мусора.


Ответ на второй вопрос:

Что касается второй части вашего вопроса о del полном удалении объектов — это не так: на самом деле в Python невозможно даже указать интерпретатору/VM удалить объект из памяти, потому что 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

Кроме того, даже когда вы вызываете del для атрибута объекта (например, del self.a), вы все равно фактически изменяете словарь self.__dict__ точно так же, как вы фактически изменяете locals()/globals(), когда делаете del a.

P.S. как Свен Маркнах указал, что del locals()['a'] на самом деле не удаляет локальную переменную a внутри функции, что правильно. Вероятно, это связано с тем, что locals() возвращает копию реальных локальных файлов. Тем не менее, ответ по-прежнему остается в силе.

person Erik Kaplun    schedule 28.04.2014
comment
Это, конечно, часто медленнее. Возможно, это даже не относится к этому конкретному сценарию, потому что удаление чего-то из середины списка также является линейным временем. В любом случае стоимость низкая по сравнению с преимуществами - person Niklas B.; 28.04.2014
comment
del a эквивалентен del locals()['a']: не совсем так, поскольку последний ничего не делает внутри функции. Вы не можете каким-либо значимым образом изменить словарь, возвращаемый locals(). (Однако вы можете изменить globals().) - person Sven Marnach; 02.05.2014
comment
@НикласБ. Если вы удаляете только одну вещь, del, вероятно, будет быстрее, чем копирование (скорее всего, потребуется сместить элементы, и потребуется сместить не более 100% из них). Однако, как и в случае с чипсами Lays, иногда люди не могут просто остановиться на одном del и действительно должны были использовать понимание списка или фильтр. - person Namey; 03.05.2014

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

# 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]

Таким образом, все они имеют свое место. Очевидно, что если вы хотите удалить число 8, пример номер 2 является лучшим вариантом, чем 1 или 3. Так что это действительно то, что имеет смысл в зависимости от обстоятельств и что является наиболее логически обоснованным.

ИЗМЕНИТЬ

Разница между arr.pop(3) и del arr[3] заключается в том, что pop возвращает удаленный элемент. Таким образом, это может быть полезно для переноса удаленных элементов в другие массивы или структуры данных. В остальном они не отличаются в использовании.

person Hunter Larco    schedule 28.04.2014
comment
Небольшое уточнение: не должен ли Удалить первый индекс списка быть del arr[0]? Если я не упускаю какой-то нюанс del? - person skytreader; 28.04.2014

Нет, я не думаю, что использование del совсем не плохо. На самом деле бывают ситуации, когда это единственный разумный вариант, например удаление элементов из словаря:

k = {'foo': 1, 'bar': 2}
del k['foo']

Возможно, проблема в том, что новички не до конца понимают, как работают переменные в Python, поэтому использование (или неправильное использование) del может быть им незнакомо.

person José Tomás Tocino    schedule 28.04.2014

Само по себе использование del неплохо; однако у него есть два аспекта, которые вносят свой вклад в определенные запахи кода:

  1. Это побочный эффект, часть последовательности шагов, и сам по себе он не имеет смысла.
  2. Возможно, del встречается в коде с ручным управлением памятью, что свидетельствует о плохом понимании области видимости Python и автоматического управления памятью. Точно так же, как оператор with более идиоматичен для обработки файловых дескрипторов, чем file.close, использование области действия и контекста более идиоматично, чем удаление элементов вручную.

Но вряд ли это канон — если бы ключевое слово del было действительно «плохим», его бы не было в ядре языка. Я просто пытаюсь сыграть в «Адвоката дьявола» — объяснить, почему некоторые программисты могут назвать его «плохим», и, возможно, дать вам возразить против него. ;)

person kojiro    schedule 28.04.2014
comment
del полностью монадический?? Я думаю, что вы рисуете очень-очень свободную и даже произвольную связь между монадой IO Haskell (которая, я согласен, является полусинонимом побочных эффектов в мире FP/Haskell) и общим понятием кода с побочными эффектами; но эта связь полностью вводит в заблуждение - если кто-то, кто не знает, что такое монада, прочитает этот ответ, он будет полностью введен в заблуждение и дезориентирован, как только увидит, что слово монада используется надлежащим образом. - person Erik Kaplun; 28.04.2014
comment
del удаляет привязку имени — как это не имеет смысла? - person Ethan Furman; 30.04.2014
comment
@EthanFurman, это вопрос дерево падает в лесу. del не имеет никакого значения, если что-то еще не наблюдает за его эффектами. В идеале компилятор должен устранить бессмысленные команды с побочными эффектами, используя DCE, но это не всегда возможно. - person kojiro; 30.04.2014
comment
del не уникален в этом. a = 1 + c не имеет смысла ни сам по себе, ни какой-либо код где бы то ни было, если и до тех пор, пока он не будет наблюдаться/использоваться другой частью системы. Не вижу причин так выделять del. - person Ethan Furman; 30.04.2014
comment
@EthanFurman Тогда спорьте с объемом всего вопроса наверху. Как я уже сказал, я на самом деле не думаю, что del это плохо. - person kojiro; 30.04.2014

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

  1. Удаление переменных из вашей текущей области. Почему вы хотите это сделать? Представьте, что вы объявляете модуль, который вычисляет переменную пакета, но потребителям этого модуля он никогда не нужен. Хотя вы можете создать для него совершенно новый модуль, это может быть излишним или может скрыть то, что на самом деле рассчитывается. Например, вам может понадобиться следующее:

    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 для удаления переменных из области видимости, когда это необходимо. То же самое относится к значениям в словарях или почти ко всему, доступ к которому осуществляется по имени или подобным неизменяемым ссылкам (свойства класса, свойства экземпляра, значения словаря и т. д.).

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

    Означает ли наличие упорядоченных и специальных индексов что-то другое для списков? Фундаментальное отличие списка состоит в том, что изменение на месте делает все ваши старые ключи в основном бесполезными, если только вы не будете очень осторожны. 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.

person Namey    schedule 03.05.2014

Что касается вопроса в вашем "EDIT",

>>> 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 все еще содержит ссылку на объект, поэтому он не уничтожен и все еще доступен.

Дополнительную информацию можно найти здесь

person WDRust    schedule 30.04.2014

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
{}
>>> 
person Community    schedule 01.05.2014