Подтвердить что ты не робот

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

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

Однако, какой смысл использовать его для "undefine" объектов?

Кроме того, почему следующий код меняет обе переменные:

>>> 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
[]
>>> 
4b9b3361

Ответ 1

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

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

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

[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, для связанного списка см. collections.deque).


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

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

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

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

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

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

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


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

Что касается второй части вашего вопроса о del удалении объектов полностью - это не так: на самом деле в Python, даже не удается сказать интерпретатору /VM удалить объект из памяти, потому что Python является сборщик мусора (например, Java, С#, Ruby, Haskell и т.д.), и это среда выполнения, которая решает, что удалить и когда.

Вместо этого, что del делает при вызове переменной (в отличие от словарного ключа или элемента списка), как это:

del a

заключается в том, что он удаляет только локальную (или глобальную) переменную, а не то, что указывает переменная (каждая переменная в Python содержит указатель/ссылку на ее содержимое, а не сам контент). Фактически, поскольку locals и globals хранятся как словарь под капотом (см. 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() возвращает копию фактических локальных жителей. Однако ответ по-прежнему остается в силе.

Ответ 2

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

Ответ 3

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

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

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

Ответ 4

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

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

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

Ответ 5

Я не думаю, что когда-либо слышал, что кто-то сказал, что 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] и отображения, вы можете иметь хороший dict {'actor' : actor, 'verb' : verb, 'object' : object}. Часто бывает так много полезного для такого рода доступа (именно поэтому мы обращаемся к функциям по имени, а не по числу): если порядок не важен, зачем делать его жестким? Если ваш заказ важен, почему вы испортили что-то, все ссылки на него недействительны (то есть, позиции элементов, расстояние между элементами).

Проблема сводится к тому, почему вы будете прямо удалять значение списка по индексу. В большинстве случаев операции, которые изменяют отдельные элементы списков на месте, имеют очевидные реализации через другие функции. Убивает предмет с заданным значением? Вы remove это. Реализация очереди или стека? Вы pop его (не блокируйте). Уменьшение количества ссылок для экземпляра в списке? l[i] = None работает так же хорошо, и ваши старые индексы все еще указывают на одни и те же вещи. Фильтрующие элементы? Вы filter или используйте понимание списка. Создание копии списка, минус некоторые элементы? Вы slice это. Как избавиться от дубликатов, хешируемых элементов? Вы можете list(set([])) или посмотреть itertools, если вам нужно просто пересечь уникальные элементы один раз.

После того, как вы избавитесь от всех этих случаев, вы получите примерно два обычных варианта использования для использования del для списка. Во-первых, вы можете удалить случайные элементы по индексу. Существует более чем несколько случаев, когда это может быть полезно, и del вполне уместно. Во-вторых, вы сохранили индексы, которые представляют, где вы находитесь в списке (т.е., Идя от комнаты к комнате в коридоре, где вы случайно разрушаете комнату иногда из Руководства по программированию Чарли Шина). Это становится жестким, если у вас более одного индекса для одного и того же списка, так как использование del означает, что все индексы должны быть соответствующим образом скорректированы. Это реже, поскольку структуры, которые вы используете с использованием индексов, часто не являются теми, которые вы удаляете из элементов (например, координатных сеток для игрового поля). Это происходит, хотя, например, while-looping над списком для опроса заданий и удаления завершенных.

Это указывает на фундаментальную проблему с удалением элементов из списка на месте по индексу: вы почти застряли, делая это по одному за раз. Если у вас есть индексы двух элементов для удаления, а затем удалить первый? Там хороший шанс, что ваш старый индекс не указывает на то, к чему он привык. Списки предназначены для хранения порядка. Поскольку del изменяет абсолютный порядок, вы застряли пешком или прыгаете по списку. Опять же, существуют твердые прецеденты (например, случайное разрушение), но есть и другие случаи, которые просто неправильны. В частности, среди новых программистов на Python люди делают ужасные вещи с циклами while на функциях (т.е. Цикл, пока не найдете значение, соответствующее вводу, del индекс). del требует индекса как ввода, и как только он будет запущен, все существующие индексы, относящиеся к этому списку, относятся к совершенно другим данным. Вы можете увидеть, где этот кошмар обслуживания, если поддерживаются несколько индексов. Опять же, это неплохо. Это просто, что на практике на практике это лучший способ сделать что-то со списком в Python.

Ответ 6

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

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

Ответ 7

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
{}
>>>