Python's del: Удаление ссылок из областей и контейнеров

Оглавление

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

В этом руководстве вы узнаете, как:

  • Писать и использовать del инструкции в Python
  • Воспользуйтесь del, чтобы удалить имена из разных областей
  • Используйте del для удаления элементов списка и ключей словаря
  • Удалить атрибуты объекта с помощью del
  • Напишите классы, которые предотвращают удаление атрибутов с помощью del

Чтобы извлечь максимальную пользу из этого руководства, вы должны быть знакомы со встроенными в Python типами данных, в частности списками и словари. Вы также должны знать основы классов и объектно-ориентированного программирования на Python.

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

Знакомство с инструкцией Python del

Инструкция Python del позволяет удалять ссылки на объекты из заданного пространства имен или типа контейнера. Он выполняет операцию, противоположную тому, что выполняет оператор присваивания. Это своего рода оператор отмены назначения, который позволяет вам отменить привязку одного или нескольких имен к объектам, на которые они ссылаются.

Это естественная функция, которая должна быть в Python. Если вы можете создать переменную, записав variable = value, то у вас должна быть возможность отменить эту операцию, удалив variable. Вот тут-то на сцену и выходит del.

Оператор del может пригодиться в различных ситуациях программирования, таких как:

  • Высвобождение ресурсов памяти
  • Предотвращение случайного использования переменных и имен
  • Как избежать конфликтов имен

Конечно, этот список неполон. Вы можете найти другие подходящие варианты использования этого оператора. В этом руководстве вы узнаете, как работает оператор del и как его использовать в вашем коде. Чтобы начать, вы начнете с изучения общего синтаксиса del в Python.

Изучение синтаксиса del

Оператор Python del состоит из ключевого слова del , за которым следует разделенный запятыми ряд ссылок .

Примечание: В этом руководстве вы будете использовать термин ссылка для общего обозначения имен или идентификаторов, которые могут содержать ссылки на объекты в Python.

Вот общий синтаксис оператора del в Python:

del reference_1[, reference_2, ..., reference_n]


Оператор del позволяет удалить одну или несколько ссылок из заданного пространства имен. Это также позволяет удалять данные из изменяемых типов контейнеров, таких как списки и словари.

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

В приведенной выше конструкции reference_* представляет собой любой идентификатор, который может содержать ссылки на конкретные объекты, хранящиеся в памяти. На практике эти ссылки могут включать в себя:

  • Идентификаторы, такие как переменные и имена функций, классов, модулей и пакетов
  • Индексы изменяемых последовательностей, таких как a_list[index]
  • Фрагменты изменяемых последовательностей, таких как a_list[start:stop:step]
  • Ключи словарей, таких как a_dict[key]
  • Элементы классов и объектов, такие как атрибуты и методы

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

Вот краткий пример использования del для удаления переменной:

>>> greeting = "Hi, Pythonista!"
>>> greeting
'Hi, Pythonista!'

>>> del greeting
>>> greeting
Traceback (most recent call last):
    ...
NameError: name 'greeting' is not defined


После выполнения инструкции del переменная greeting больше не доступна. Если вы попытаетесь получить доступ к greeting, то получите NameError, потому что эта переменная больше не существует.

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

Понимание последствий del

Как вы уже узнали, оператор del в Python удаляет ссылки из пространств имен или контейнеров данных. Он не удаляет конкретные объекты. Например, вы не можете удалить литералы встроенных типов с помощью del:

>>> del 314
  File "<input>", line 1
    del 314
        ^^
SyntaxError: cannot delete literal

>>> del "Hello, World!"
  File "<input>", line 1
    del "Hello, World!"
        ^^^^^^^^^^^^^^^
SyntaxError: cannot delete literal


В этих примерах обратите внимание, что вы не можете использовать оператор del непосредственно для объектов. Вы должны использовать его с переменными, именами и другими идентификаторами, как вы уже узнали:

>>> number = 314
>>> del number


Однако выполнение del, как в этом примере, не означает, что вы удаляете число 314 из памяти. Это только удаляет имя number из вашего текущего пространства имен или области .

Когда вы передаете идентификатор — например, имя переменной, класса, функции или метода — в качестве аргумента del, оператор отсоединяет идентификатор от объекта, на который ссылается ссылка, и удаляет идентификатор из содержащего его пространства имен. Однако указанный объект может быть удален из памяти вашего компьютера не сразу.

Аналогично, когда вы используете индекс списка, фрагмент или ключ словаря в качестве аргумента для del, вы удаляете ссылку на целевой элемент, фрагмент или ключ из содержащей его структуры данных. Это может не означать немедленного удаления указанного объекта из памяти вашего компьютера.

Короче говоря, del не удаляет объекты из памяти и не освобождает используемое пространство. Он удаляет только ссылки на объекты. Такое поведение может вызвать вопрос. Если del не удаляет объекты из памяти, то как вы можете использовать del для освобождения ресурсов памяти во время выполнения вашего кода?

Чтобы ответить на этот вопрос, вам нужно понять, как Python управляет удалением объектов из памяти. В следующем разделе вы узнаете о системе сбора мусора в Python и о том, как она соотносится с использованием del для освобождения памяти в ваших программах.

Распутывание del против сборки мусора

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

По сути, Python отслеживает, сколько идентификаторов содержат ссылки на каждый объект в любой момент времени. Количество идентификаторов, указывающих на данный объект, называется количеством ссылок на объект.

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

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

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

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

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

Другим действием, снижающим количество ссылок, является вывод данной ссылки за пределы области видимости, неявное удаление ссылки.

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

Python автоматически вызывает .__del__(), когда данный объект вот-вот будет уничтожен. Этот вызов позволяет объекту освободить внешние ресурсы и очистить себя. Важно отметить, что оператор del не запускает метод .__del__().

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

Использование del вместо назначения None

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

>>> number = 314
>>> number
314
>>> number = None
>>> print(number)
None


При переназначении переменной number на None количество ссылок на объект 314 уменьшается до нуля. Из-за этого Python может выполнять сборку объекта, освобождая соответствующую память. Однако это присваивание не удаляет имя переменной из вашей текущей области видимости, как это сделал бы оператор del.

Поскольку None является одноэлементным объектом, встроенным в Python, это назначение не выделяет и не использует новую память, но сохраняет вашу переменную активной и доступной в вашей текущей области видимости.

Подход None может быть полезен, если вы хотите предотвратить исключение NameError, возникающее при попытке удалить имя или владельца ссылки, которые не существуют в вашем текущем пространстве имен. В этом случае Python автоматически создаст новую переменную и назначит None в качестве ее начального значения.

Удаление имен из области видимости

Удаление имени из заданной области - это первый вариант использования del, о котором вы узнаете в этом руководстве. Область действия данного имени или идентификатора - это область программы, в которой вы можете однозначно получить доступ к этому имени.

В Python вы найдете не более четырех областей:

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

>>> color = "blue"
>>> fruit = "apple"
>>> pet = "dog"

>>> del color
>>> color
Traceback (most recent call last):
    ...
NameError: name 'color' is not defined

>>> del fruit, pet
>>> fruit
Traceback (most recent call last):
    ...
NameError: name 'fruit' is not defined
>>> pet
Traceback (most recent call last):
    ...
NameError: name 'pet' is not defined


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

Примечание: Вы можете просмотреть все имена, которые находятся в вашей глобальной и встроенной областях, используя встроенную функцию globals(). Аналогично, вы можете получить доступ к именам в вашей текущей локальной области с помощью функции locals(). Обе функции возвращают объекты словаря, сопоставляющие имена объектам в их целевых областях.

Вы также можете удалить имена из локальной и закрытой областей в своих пользовательских функциях, используя del таким же образом. Однако вы не можете удалить имена из встроенной области:

>>> del list
Traceback (most recent call last):
    ...
NameError: name 'list' is not defined
>>> list()
[]

>>> del dict
Traceback (most recent call last):
    ...
NameError: name 'dict' is not defined
>>> dict()
{}

>>> del max
Traceback (most recent call last):
    ...
NameError: name 'max' is not defined
>>> max([3, 2, 5])
5


Если вы попытаетесь удалить любое встроенное имя с помощью инструкции del, то получите NameError. Сообщение об ошибке может показаться непонятным, поскольку вы можете получить доступ ко всем этим именам, как если бы они находились в глобальной области. Однако эти имена находятся во встроенной области, которая недоступна напрямую с помощью del.

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

Скажите, что вы изучаете списки, и запустите следующий код:

>>> list = [1, 2, 3, 4]
>>> list
[1, 2, 3, 4]


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

>>> list(range(10))
Traceback (most recent call last):
    ...
TypeError: 'list' object is not callable


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

>>> del list  # Remove the redefined name
>>> list()  # Now the original name is available again
[]


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

Удаление Элементов из Изменяемых Коллекций

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

Вы также можете использовать инструкцию del, чтобы удалить несколько элементов из списка за один раз. Для этого вы можете использовать синтаксис slice. Для этого конкретного случая использования вы не найдете метод, который выполняет эту задачу. Итак, del это ваш путь.

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

Удаление Элементов из списка

Вы уже знаете, что синтаксис индексации списка, a_list[index], позволяет вам получать доступ к отдельным элементам списка. Этот синтаксис предоставляет идентификатор, который содержит ссылку на элемент в целевом индексе.

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

del a_list[index]


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

>>> computer_parts = [
...     "CPU",
...     "motherboard",
...     "case",
...     "monitor",
...     "keyboard",
...     "mouse"
... ]

>>> del computer_parts[3]
>>> computer_parts
['CPU', 'motherboard', 'case', 'keyboard', 'mouse']

>>> del computer_parts[-1]
>>> computer_parts
['CPU', 'motherboard', 'case', 'keyboard']


В этом фрагменте кода вы создаете список, содержащий некоторые компоненты компьютера. Затем вы используете del, чтобы удалить "monitor", элемент с индексом 3. Наконец, вы удаляете последний элемент из списка, используя отрицательный индекс -1.

Примечание: Оператору del не удается удалить элементы из неизменяемых типов последовательностей, таких как строки, байт и кортежей:

>>> site = "realpython.com/"
>>> del site[-1]
Traceback (most recent call last):
    ...
TypeError: 'str' object doesn't support item deletion

>>> color = (255, 0, 0)
>>> del color[0]
Traceback (most recent call last):
    ...
TypeError: 'tuple' object doesn't support item deletion


Удаление элемента из этого кортежа повлекло бы за собой мутацию на месте, которая недопустима в неизменяемых типах коллекций.

Будьте осторожны при использовании расширенного синтаксиса del для удаления нескольких элементов из списка. В конечном итоге вы можете удалить не те элементы или даже получить ошибку. IndexError.

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

>>> sample = [3, None, 2, 4, None, 5, 2]

>>> del sample[1], sample[4]
>>> sample
[3, 2, 4, None, 2]


Что только что произошло? Вы не удалили второе None, а вместо этого удалили число 5. Проблема в том, что del последовательно обрабатывает свои аргументы слева направо. В этом примере del сначала удаляет второй элемент из sample. Затем из измененного списка удаляется четвертый элемент, 5. Чтобы получить желаемый результат, вам нужно учитывать, что список целей меняется после каждого удаления.

Возможным обходным путем является удаление элементов справа налево:

>>> sample = [3, None, 2, 4, None, 5, 2]

>>> del sample[4], sample[1]
>>> sample
[3, 2, 4, 5, 2]


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

Когда вы используете индекс списка в качестве аргумента для del, Python возвращается к вызову специального метода .__delitem__(), который выполняет фактическое удаление . Вот пример, иллюстрирующий такое поведение:

>>> class List(list):
...     def __delitem__(self, index):
...         print(
...             f"Running .__delitem__() to delete {self[index]}"
...         )
...         super().__delitem__(index)
...

>>> sample = List([3, None, 2, 4, None, 5, 2])
>>> del sample[1]
Running .__delitem__() to delete None

>>> del sample[3]
Running .__delitem__() to delete None


В этом примере вы создаете подкласс встроенного класса list и переопределяете его метод .__delitem__(). Внутри метода вы выводите сообщение, показывающее, что метод вызывается автоматически. Затем вы используете super() для вызова исходной реализации .__delitem__() в родительском классе, list.

Примечание: Чтобы узнать больше о подклассах встроенного типа list, ознакомьтесь с Пользовательскими списками Python: наследование от list vs UserList.

Говоря о методах, в списках Python есть методы .remove() и .pop(), которые позволяют удалять элемент по значению или индексу соответственно:

>>> pets = ["dog", "cat", "fish", "bird", "hamster"]

>>> pets.remove("fish")  # Equivalent to del pets[2]
>>> pets
['dog', 'cat', 'bird', 'hamster']

>>> pets.pop(3)
'hamster'
>>> pets.pop()
'bird'
>>> pets
['dog', 'cat']


При вызове .remove() вы удаляете элемент "fish" из своего списка pets. Вызов этого метода эквивалентен запуску del pets[2].

Использование del против .remove() будет зависеть от того, что вы пытаетесь сделать. Если вы хотите удалить элементы на основе их индекса, не зная их содержимого, то del - это решение, поскольку оно работает с индексами элементов. С другой стороны, если вы хотите удалить элементы списка с известным содержимым, то .remove() может быть более удобочитаемым и понятным. В нем будет четко указано, что вы хотите удалить это конкретное значение.

Затем вы используете .pop(), чтобы удалить элемент с индексом 3. Помимо удаления целевого элемента, .pop() также возвращает его. Вот почему на экране отображается 'hamster'. Наконец, вызов .pop() без целевого индекса удаляет и возвращает конечный элемент из целевого списка.

Использование del вместо .pop() в еще большей степени зависит от ваших конкретных потребностей. В этом случае оба варианта работают с индексами товаров. Отличительной особенностью является возвращаемое значение. Если вас не интересует возвращаемое значение, то используйте del. Если вам нужно возвращаемое значение для дальнейших вычислений, то вы должны использовать .pop().

Удаление фрагмента из списка

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

>>> del a_list[start:stop:step]


Синтаксис разбиения принимает до трех аргументов, разделенных двоеточием: start, stop, и step. Они определяют индекс, с которого начинается срез, индекс, на котором срез должен заканчиваться для получения значений, и шаг между значениями.

Вот несколько примеров использования описанной выше конструкции для удаления фрагментов из списков:

>>> digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> del digits[3:6]
>>> digits
[0, 1, 2, 6, 7, 8, 9]

>>> del digits[:3]
>>> digits
[6, 7, 8, 9]

>>> del digits[2:]
>>> digits
[6, 7]


В первом примере вы удаляете все элементы из индекса 3 в 6. Обратите внимание, что интервал нарезки открыт на stop, что означает, что элемент с индексом 6 не включается в удаление. Другими словами, операция нарезки останавливается при достижении индекса stop без включения элемента с этим индексом.

Во втором примере вы не используете значение start, что означает, что вы хотите начать удаление с первого элемента в списке. Наконец, в третьем примере вы не используете значение stop. Это означает, что вы хотите удалить все элементы из 2 в конец списка.

Вы также можете использовать step, чтобы определить, сколько элементов вы хотите пропустить во время нарезки. Рассмотрим следующий пример:

>>> numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> del numbers[::2]
>>> numbers
[2, 4, 6, 8]


В этом примере вы используете step из 2, чтобы удалить все остальные элементы из numbers. Это ловкий трюк, который удаляет нечетные числа из вашего списка.

Вы можете использовать объект slice вместо оператора нарезки, чтобы удалять элементы из ваших списков:

>>> numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> every_other = slice(0, None, 2)

>>> del numbers[every_other]
>>> numbers
[2, 4, 6, 8]


В этом примере вы используете встроенный класс slice() для создания объекта slice. Конструктор класса принимает три аргумента start, stop, и step. Они имеют то же значение, что и эквивалентные индексы в операторе нарезки.

Этот конкретный объект slice извлекает элементы, начиная с 0 и до конца списка, что можно выполнить, установив для аргумента stop значение None. Аргумент step принимает значение 2. Этот фрагмент удаляет все остальные элементы из целевого списка.

Интересно, что удаление фрагмента элементов из существующего списка имеет тот же эффект, что и назначение пустого списка тому же фрагменту:

>>> digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> digits[3:6] = []
>>> digits
[0, 1, 2, 6, 7, 8, 9]

>>> digits[:3] = []
>>> digits
[6, 7, 8, 9]

>>> digits[2:] = []
>>> digits
[6, 7]


Присвоение данному фрагменту пустого списка приводит к тому же эффекту, что и удаление целевого фрагмента с помощью инструкции del. Этот пример эквивалентен его первой версии, приведенной в начале этого раздела.

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

Удаление Ключей из Словарей

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

del a_dict[key]


Этот оператор удаляет key и связанное с ним значение из содержащего его словаря a_dict. Чтобы увидеть эту конструкцию в действии, запустите следующий пример:

>>> vegetable_prices = {
...     "carrots": 0.99,
...     "spinach": 1.50,
...     "onions": 0.50,
...     "cucumbers": 0.75,
...     "peppers": 1.25,
... }

>>> del vegetable_prices["spinach"]
>>> del vegetable_prices["onions"], vegetable_prices["cucumbers"]

>>> vegetable_prices
{'carrots': 0.99, 'peppers': 1.25}


Здесь вы используете del, чтобы удалить ключи "spinach", "onions", и "cucumbers" из вашего словаря vegetable_prices. В случае со словарями совершенно безопасно удалять несколько ключей в одной строке, используя расширенный синтаксис del, поскольку ключи уникальны.

Вы также можете использовать некоторые методы для удаления ключей из словаря. Например, вы можете использовать методы .pop() и .popitem():

>>> school_supplies = {
...     "pencil": 0.99,
...     "pen": 1.49,
...     "notebook": 2.99,
...     "highlighter": 0.79,
...     "ruler": 1.29,
... }

>>> school_supplies.pop("notebook")
2.99

>>> school_supplies.popitem()
('ruler', 1.29)
>>> school_supplies.popitem()
('highlighter', 0.79)

>>> school_supplies
{'pencil': 0.99, 'pen': 1.49}


В первом примере вы используете .pop() для удаления ключа "notebook" и связанного с ним значения 2.99 из вашего словаря school_supplies. Вы должны вызвать .pop() с существующим ключом в качестве аргумента. Во втором примере вы вызываете .popitem(), чтобы удалить последний добавленный элемент из school_supplies.

Несмотря на то, что эти методы удаляют ключи из существующих словарей, они работают несколько иначе, чем del. Помимо удаления целевого ключа, .pop() возвращает значение, связанное с этим ключом. Аналогично, .popitem() удаляет ключ и возвращает соответствующую пару ключ-значение в виде кортежа. Оператор del не возвращает удаленное значение.

Python автоматически вызывает .__delitem__() специальный метод, когда вы используете del для удаления ключа из словаря. Вот пример, иллюстрирующий такое поведение:

>>> class Dict(dict):
...     def __delitem__(self, key) -> None:
...         print(f"Running .__delitem__() to delete {(key, self[key])}")
...         super().__delitem__(key)
...

>>> ordinals = Dict(
...     {"First": "I", "Second": "II", "Third": "III", "Fourth": "IV"}
... )

>>> del ordinals["Second"]
Running .__delitem__() to delete ('Second', 'II')

>>> del ordinals["Fourth"]
Running .__delitem__() to delete ('Fourth', 'IV')


В этом примере вы переопределяете метод .__delitem__() в классе Dict, который наследуется от встроенного класса dict. Выполнение инструкции del с ключом Dict в качестве аргумента запускает вашу пользовательскую реализацию .__delitem__(), которая выводит сообщение, содержащее информацию об удаленном элементе.

Чтобы узнать больше о том, как безопасно создать подкласс встроенного типа dict в Python, ознакомьтесь с Пользовательскими словарями Python: наследование от dict и UserDict.

Удаление Элементов из Изменяемых Вложенных Коллекций

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

Возможность доступа к вложенным элементам позволяет использовать del для их удаления, используя следующий синтаксис:

# Syntax for sequences
del sequence[outer_index][nested_index_1]...[nested_index_n]

# Syntax for dictionaries
del dictionary[outer_key][nested_key_1]...[nested_key_n]

# Syntax for combined structures
del sequence_of_dicts[sequence_index][dict_key]
del dict_of_lists[dict_key][list_index]


Чтобы получить доступ к объектам из вложенных изменяемых коллекций и удалить их, вы должны использовать оператор индексирования, указав соответствующие индексы слева направо. Подсчитайте, сколько уровней вложенности вам нужно пройти, чтобы добраться до целевого элемента. Вот сколько индексов вы должны указать.

Вот пример того, как удалить вложенные элементы из списка списков:

>>> matrix = [
...     [1, 2, 3],
...     [4, 5, 6],
...     [7, 8, 9]
... ]

>>> target_row = 0
>>> target_col = 2
>>> del matrix[target_row][target_col]
>>> matrix
[
    [1, 2],
    [4, 5, 6],
    [7, 8, 9]
]

>>> target_row = 1
>>> del matrix[target_row][target_col]
>>> matrix
[
    [1, 2],
    [4, 5],
    [7, 8, 9]
]

>>> target_row = 2
>>> del matrix[target_row][target_col]
>>> matrix
[
    [1, 2],
    [4, 5],
    [7, 8]
]


В этом примере вы использовали del для удаления вложенных элементов в вашем списке matrix. В частности, вы удалили последнее значение из каждой строки матрицы. Использование описательных имен для индексов позволяет улучшить читаемость вашего кода, упрощая его использование.

Как вы можете видеть, target_row представляет индекс вложенного списка в matrix, в то время как target_col представляет индекс целевого элемента во вложенном списке. Вот как вы получаете доступ к вложенным элементам.

Несмотря на то, что кортежи Python являются неизменяемыми типами данных, которые не допускают прямого удаления, они могут хранить списки, словари и другие изменяемые типы, которые допускают удаление. Таким образом, вы также можете использовать del для вложенных изменяемых объектов в ваших кортежах.

Чтобы проиллюстрировать это поведение, предположим, что вы работаете с цветами в цветовой модели RGB. Для представления каждого цвета используется кортеж, поскольку цвета должны быть постоянными. Вы решаете создать свои цветные объекты, указав название в верхнем регистре и список значений RGB:

>>> red = ("RED", [255, 0, 0])
>>> blue = ("BLUE", [0, 0, 255])


Эти цвета выглядят великолепно. У них есть название и список значений RBG. Вы не можете удалить элементы первого уровня в этих кортежах, потому что кортежи неизменяемы. Однако вы можете удалить элементы из вложенных списков, содержащих значения RGB:

>>> del red[1][0]
>>> red
('RED', [0, 0])


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

Другой распространенной практикой в Python является вложение списков в словари. Этот тип конструкции часто используется при работе с файлами JSON, которые предоставляют понятный и читаемый формат для обмена информацией.

Рассмотрим следующий пример словаря, в котором товары хранятся по классам:

>>> products = {
...     "fruits": ["apple", "banana", "orange"],
...     "veggies": ["tomato", "avocado", "pepper"]
... }

>>> del products["fruits"][1]
>>> products
{
    'fruits': ['apple', 'orange'],
    'veggies': ['tomato', 'avocado', 'pepper']
}

>>> del products["veggies"][0]
>>> products
{
    'fruits': ['apple', 'orange'],
    'veggies': ['avocado', 'pepper']
}


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

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

Вот еще один пример. На этот раз у вас есть список словарей. Каждый вложенный словарь содержит другой словарь:

>>> prices = [
...     {"fruits": {"apple": 1.2, "banana": 1.3, "orange": 1.5}},
...     {"veggies": {"tomato": 2.1, "avocado": 3.2, "pepper": 1.8}},
... ]

>>> del prices[0]["fruits"]["banana"]

>>> prices
[
    {'fruits': {'apple': 1.2, 'orange': 1.5}},
    {'veggies': {'tomato': 2.1, 'avocado': 3.2, 'pepper': 1.8}}
]

>>> del prices[0]["fruits"]
>>> prices
[{}, {'veggies': {'tomato': 2.1, 'avocado': 3.2, 'pepper': 1.8}}]


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

Удаление Участников Из Пользовательских Классов

Когда дело доходит до пользовательских классов, вы можете удалить как атрибуты класса, так и экземпляра, используя инструкцию del. Вы также можете использовать del для удаления методов из ваших классов. Удаление членов классов и экземпляров может быть полезным лишь в редких случаях, но в Python это возможно.

Общий синтаксис для удаления членов класса с помощью del выглядит следующим образом:

del a_class.class_attribute
del a_class.method

del an_instance.instance_attribute


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

Удаление членов класса: Общий пример

Чтобы проиллюстрировать, как работает синтаксис del для удаления членов класса, напишите следующий класс SampleClass в вашем Python interactive REPL:

>>> class SampleClass:
...     class_attribute = 0
...     def __init__(self, arg):
...         self.instance_attribute = arg
...     def method(self):
...         print(self.instance_attribute)
...

>>> SampleClass.__dict__
mappingproxy({
    '__module__': '__main__',
    'class_attribute': 0,
    '__init__': <function SampleClass.__init__ at 0x105534360>,
    'method': <function SampleClass.method at 0x104bcf240>,
    ...
})

>>> sample_instance = SampleClass("Value")
>>> sample_instance.__dict__
{'instance_attribute': 'Value'}


В этом фрагменте кода вы создаете пример класса с атрибутом class, атрибутом instance и методом. Выделенные строки показывают, как эти элементы взаимодействуют с классом и экземпляром .__dict__ атрибуты, которые хранят атрибуты класса и экземпляра соответственно.

Теперь выполните следующие действия, чтобы удалить члены этого класса и его объект:

>>> # Delete members through the instance
>>> del sample_instance.instance_attribute

>>> del sample_instance.class_attribute
Traceback (most recent call last):
    ...
AttributeError: 'SampleClass' object has no attribute 'class_attribute'
>>> del sample_instance.method
Traceback (most recent call last):
    ...
AttributeError: 'SampleClass' object has no attribute 'method'

>>> sample_instance.__dict__
{}

>>> # Delete members through the class
>>> del SampleClass.class_attribute
>>> del SampleClass.method

>>> SampleClass.__dict__
mappingproxy({
    '__module__': '__main__',
    '__init__': <function SampleClass.__init__ at 0x105534360>,
    ...
})


Используя экземпляр данного класса, вы можете удалить только атрибуты экземпляра. Если вы попытаетесь удалить атрибуты и методы класса, то получите исключение AttributeError. Вы можете удалить атрибуты и методы класса только через сам класс, как вы можете убедиться в последних двух примерах.

Удаление атрибута экземпляра: Практический пример

Когда del будет полезно для удаления атрибутов экземпляра? Когда вам нужно уменьшить объем памяти вашего объекта, подготовив временные атрибуты экземпляра для системы сбора мусора Python, что может привести к освобождению ценных ресурсов памяти на вашем компьютере.

Например, предположим, что вы хотите написать класс Factorial для представления факториала числа. Конструктор класса должен принимать число в качестве аргумента и создавать экземпляр с двумя атрибутами, доступными только для чтения, .number и .factorial.

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

Вот возможная реализация для вашего класса Factorial:

 1# factorial.py
 2
 3class Factorial:
 4    def __init__(self, number):
 5        self._number = number
 6        self._cache = {0: 1, 1: 1}
 7        self._factorial = self._calculate_factorial(number)
 8        del self._cache
 9
10    def _calculate_factorial(self, number):
11        if number in self._cache:
12            return self._cache[number]
13        current_factorial = number * self._calculate_factorial(number - 1)
14        self._cache[number] = current_factorial
15        return current_factorial
16
17    @property
18    def number(self):
19        return self._number
20
21    @property
22    def factorial(self):
23        return self._factorial
24
25    def __str__(self) -> str:
26        return f"{self._number}! = {self._factorial}"
27
28    def __repr__(self):
29        return f"{type(self).__name__}({self._number})"


Ничего себе! В этом коде многое происходит. Вот разбивка наиболее важных строк:

  • Строка 4 определяет инициализатор, который принимает введенный вами номер в качестве аргумента.

  • Строка 5 определяет непубличный атрибут экземпляра с именем ._number для хранения введенного вами номера.

  • Строка 6 определяет другой атрибут экземпляра, называемый ._cache, который будет работать как кэш для промежуточных вычислений. Изначально этот атрибут содержит факториал 0 и 1.

  • Строка 7 определяет атрибут ._factorial, который содержит факториал входного числа. Обратите внимание, что этот атрибут получает свое значение в результате вызова ._calculate_factorial().

  • В строке 8 используется del для удаления кэшированных данных. Это удаление позволит вам освободить память для последующего использования и сделает ваши экземпляры Factorial более легкими, а ваш код - более экономичным с точки зрения памяти.

  • Строки с 10 по 15 определяют метод ._calculate_factorial(), который вычисляет факториал вашего входного числа, используя ._cache для сохранения уже вычисленных значений.

  • Строки с 17 по 23 определяют два свойства для управления атрибутами .number и .factorial . Обратите внимание, что у этих свойств нет методов настройки, что делает базовые атрибуты доступными только для чтения.

  • Строки с 25 по 26 определяют .__str__() метод, который возвращает удобное для пользователя строковое представление для ваших экземпляров.

  • Строки с 28 по 29 определяют метод .__repr__(), который возвращает удобное для разработчика строковое представление объектов Factorial.

Вот как работает ваш класс на практике:

>>> from factorial import Factorial

>>> for i in range(5):
...     print(Factorial(i))
...
0! = 1
1! = 1
2! = 2
3! = 6
4! = 24

>>> factorial_5 = Factorial(5)
>>> factorial_5
Factorial(5)
>>> factorial_5.number
5
>>> factorial_5.factorial
120
>>> print(factorial_5)
5! = 120

>>> factorial_5.factorial = 720
Traceback (most recent call last):
    ...
AttributeError: property 'factorial' of 'Factorial' object has no setter

>>> factorial_5._cache
Traceback (most recent call last):
    ...
AttributeError: 'Factorial' object has no attribute '_cache'


В первом примере вы запускаете цикл for, который создает пять экземпляров Factorial и выводит их на экран. Обратите внимание, как работает метод .__str__(), выводя экземпляр с использованием математической факториальной записи.

Затем вы создаете другой экземпляр Factorial с 5 в качестве входного номера. Обратите внимание, что вы можете получить доступ к атрибутам .number и .factorial, но не можете переназначить их, поскольку они доступны только для чтения. Наконец, если вы попытаетесь получить доступ к непубличному атрибуту ._cache, то получите значение AttributeError, потому что вы уже удалили этот атрибут в методе .__init__().

Удаление атрибута ._cache после вычисления факториала приводит к экономии памяти в экземплярах Factorial. Чтобы проиллюстрировать это, добавьте библиотеку Pympler, установив pympler с pip. Эта библиотека позволит вам измерить объем памяти, занимаемый вашими Factorial экземплярами.

Затем выполните следующий код:

>>> from pympler import asizeof

>>> asizeof.asizeof(Factorial(100))
600


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

Теперь вернитесь к своему файлу factorial.py и закомментируйте строку 8, которая содержит инструкцию del, которая удаляет ._cache. После внесения этого изменения продолжайте и снова запустите вышеуказанный вызов функции:

>>> asizeof.asizeof(Factorial(100))
14216


Теперь размер вашего экземпляра Factorial более чем в 23 раза больше, чем раньше. Это результат удаления, а не сохранения ненужных кэшированных данных после завершения создания экземпляра.

Предотвращение удаления атрибутов в пользовательских классах

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

  1. Предоставление пользовательского метода .__delattr__()
  2. Используя property с помощью метода удаления

Чтобы начать, вы начнете с переопределения специального метода .__delattr__() с помощью пользовательской реализации. По сути, Python автоматически вызывает этот метод, когда вы используете данный атрибут экземпляра в качестве аргумента инструкции del.

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

# non_deletable.py

class NonDeletable:
    def __init__(self, value):
        self.value = value

    def __delattr__(self, name):
        raise AttributeError(
            f"{type(self).__name__} object doesn't support attribute deletion"
        )


В этом классе вы переопределяете специальный метод .__delattr__(). Ваша реализация вызывает исключение AttributeError всякий раз, когда пользователь пытается удалить какой-либо атрибут, например, .value, используя инструкцию del.

Вот пример того, как ваш класс ведет себя на практике:

>>> one = NonDeletable(1)
>>> one.value
1

>>> del one.value
Traceback (most recent call last):
    ...
AttributeError: NonDeletable object doesn't support attribute deletion


Всякий раз, когда вы пытаетесь удалить атрибут .value любого экземпляра NonDeletable, вы получаете исключение AttributeError, которое возникает из вашей пользовательской реализации .__delattr__(). Этот метод обеспечивает быстрое решение для тех ситуаций, когда вам необходимо предотвратить удаление атрибута экземпляра.

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

# person.py

class Person:
    def __init__(self, name):
        self.name = name

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        self._name = str(value).upper()

    @name.deleter
    def name(self):
        raise AttributeError("can't delete attribute 'name'")


В этом примере Person имеет атрибут .name, который вы реализуете с помощью свойства. У атрибута есть метод получения, который возвращает значение ._name, которое является обычным атрибутом, в котором хранятся данные.

Метод setter принимает новое значение для атрибутов, преобразует его в строку и преобразует в заглавные буквы.

Наконец, метод deleter генерирует значение AttributeError, сигнализирующее о том, что атрибут не подлежит удалению. Python вызывает этот метод автоматически, когда вы используете атрибут .name в качестве аргумента в инструкции del:

>>> from person import Person

>>> jane = Person("Jane")
>>> jane.name
'JANE'
>>> jane.name = "Jane Doe"
>>> jane.name
'JANE DOE'

>>> del jane.name
Traceback (most recent call last):
    ...
AttributeError: can't delete attribute 'name'


Ваш класс Person работает должным образом. Вы можете получить доступ к его атрибуту .name и обновлять его новыми значениями, каждый раз вводя значение в верхнем регистре. Метод deleter вашего свойства .name в конце определения класса гарантирует, что ваши пользователи не смогут удалить атрибут.

Заключение

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

В этом руководстве вы узнали, как:

  • Напишите del инструкции в вашем коде на Python
  • Удалить имен из разных пространств имен с помощью del
  • Быстрое удаление элементов списка и ключей словаря с помощью del
  • Используйте del для удаления атрибутов для пользовательских классов и объектов
  • Предотвратить удаление атрибутов в ваших пользовательских классах

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

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

<статус завершения article-slug="python-del-statement" class="btn-group mb-0" data-api-article-bookmark-url="/api/v1/articles/python-del-statement/bookmark/" data-api-article-завершение-status-url="/api/v1/articles/python-del-statement/завершение_статуса/"> <кнопка поделиться bluesky-text="Интересная статья на #Python от @realpython.com :" email-body="Ознакомьтесь с этой статьей о Python:%0A%0apython's del: Удаление ссылок из областей и контейнеров" email-subject="Статья о Python для вас" twitter-text="Интересная #Статья о Python от @realpython:" url="https://realpython.com/python-del-statement /" url-title="Python's del: удаление ссылок из областей и контейнеров"> Back to Top