Польза-дель-плохо?


Я обычно использую 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 46

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/ за потрясающее вступление. И вы не должны слишком много думать об оптимизации при разработке своей программы (см. https://softwareengineering.stackexchange.com/questions/80084/is-premature-optimization-really-the-root-of-all-evil)кроме того, удаление элемента из списка довольно дорого, если только это не связанный список (чего нет в Python 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

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

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

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. Так что это действительно то, что имеет смысл в зависимости от обстоятельств и что наиболее логично.

EDIT

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

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

k = {'foo': 1, 'bar': 2}
del k['foo']
Возможно, проблема в том, что новички не до конца понимают, как переменные работают в Python, поэтому использование (или неправильное использование) del может быть незнакомым.

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

    Это побочный эффект, часть последовательности шагов, и сам по себе он не имеет смысла.
  1. возможно, что del встречается в коде, который имеет ручное управление памятью, что свидетельствует о плохом понимании области Python и автоматического управления памятью. Точно так же, как оператор with более идиоматичен для обработки дескрипторов файлов, чем file.close, используя scope и контекст более идиоматичен, чем ручная атомизация членов.
Но это вряд ли канон – если бы ключевое слово del было действительно "плохим", его не было бы в ядре языка. Я просто пытаюсь играть роль адвоката дьявола-объяснить, почему некоторые программисты могут называть его "плохим" и, возможно, дать вам позицию, с которой можно спорить. ;)

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

  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.

Относительно 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
{}
>>>