Definition
A running program piles up things it no longer needs: a configuration key that has gone stale, a variable that must not be reused further down. Most of the time, ignoring them is enough; sometimes the data really gets in the way, and it has to go.
del is the word Python keeps for that gesture. It is a statement, not a function: it takes no parentheses, returns nothing, and can therefore never sit to the right of an equals sign. That special status has a reason: it acts on the target itself, whereas a function only receives the contents of a variable, never the label pointing at it.
Depending on what follows it, del wipes a name from the current namespace, an item from a list, an entry from a dictionary or an attribute from an object. Here are the two most common uses.
stock = {"apples": 12, "pears": 4}
del stock["pears"]
print(stock)
# {'apples': 12}
numbers = [10, 20, 30, 40]
del numbers[1]
print(numbers)
# [10, 30, 40]The collection is changed in place: the original object is the one that moved, and nothing comes out of the operation, not even the item that was removed.
That is where del parts ways with pop and remove, two methods attached to one precise type. It belongs to the grammar, like if or return, so it behaves identically on unrelated objects.
Removing a name is not removing the object
Here is the misunderstanding that costs the most: many people read del as an order to destroy, room handed back to memory. That is not what happens.
A variable is not a box holding a value, it is a label stuck on an object. Two labels can point at the same object, which happens as soon as a list is passed to a function. What del peels off is the label.
a = [1, 2, 3]
b = a # two names, a single object
del a
print(b) # [1, 2, 3]: the object is still aliveThe name a has left the current namespace, and using it again raises a NameError. The object itself is doing fine, since b still points at it.
This statement therefore manages names, not memory. Python counts the references pointing at each object and hands the room back once that counter drops to zero: removing the last reference does free the object, but as a consequence, never as a promise.
Not to be confused with __del__, the magic method called at the moment an object is actually destroyed. The del statement only triggers it when it removes the last reference.
What it wipes depending on the target
The same statement covers five forms. What changes from one line to the next is not the effect, which is fairly predictable, but the way it fails: that is the column to read first when the target comes from a file or from the network.
| Form | Effect | If the target is missing |
|---|---|---|
del name | Removes the name from the current namespace | NameError |
del items[2] | Removes the item at position 2 | IndexError |
del items[1:3] | Removes a whole slice | Nothing, slicing is forgiving |
del prices["key"] | Removes the key and value pair | KeyError |
del obj.attribute | Removes the attribute from the instance | AttributeError |
The slice row stands out: a slice (slicing) does not ask for one precise position but for whatever sits between two bounds. When that area is empty, there is nothing to remove, so nothing to report.
One limit is missing from the table: the target has to be mutable. On an immutable object, a string or a tuple, removing an item is turned down with a TypeError. Wiping the whole name is still possible, without touching the contents.
Then there is scope: inside a function, wiping a variable defined outside means declaring it global beforehand, failing which Python targets the local variable of the same name and complains that it does not exist.
del, pop, remove and clear
Four tools remove items from a list, and each one answers a different request.
| Tool | What it asks for | What it hands back |
|---|---|---|
del items[i] | A position | Nothing |
items.pop(i) | A position | The removed item |
items.remove(v) | A value | Nothing, and only the first match goes |
items.clear() | Nothing | Nothing, the list is emptied in place |
The choice rests on what you plan to do with the item. If you still need it, pop hands it over along the way; if you do not, del says precisely that, without letting the next reader believe a value is being collected somewhere.
On a dictionary, pop also accepts a fallback value, often None, returned when the key is missing instead of a KeyError: the forgiving version, for keys whose absence is nothing unusual.
The trap of removing while walking through
This last point sends a lot of people looking for help, and for a precise reason: the program does not crash, it hands back a wrong result. Removing items from a list while walking through it shifts the positions under the feet of the loop. When the item at position 1 disappears, the old position 2 slides into position 1, but the loop asks for position 2, and the item that just slid is never examined.
numbers = [1, 2, 2, 3]
# Wrong: the list changes during the walk
for i, n in enumerate(numbers):
if n == 2:
del numbers[i]
# [1, 2, 3]: one 2 survived
# Right: rebuild rather than remove
numbers = [n for n in numbers if n != 2]One 2 survived and nothing flagged it. On four items the oddity shows up; on ten thousand lines it ships to production and only surfaces the day a total refuses to add up.
Never change a list while you are walking through it. Build the list you want with a list comprehension instead, as in the second half of the example: the result is correct and reads back without effort.
Dictionaries are blunter: changing their size mid-iteration raises an exception straight away instead of skipping entries in silence. Rebuilding stays easier to read anyway than a backwards walk, the other classic answer.
Frequently asked questions
Does del free memory?
Not directly. It removes one reference, and the room is only handed back once the last one disappears. On an object stored inside another list, the memory stays occupied as before and the object remains reachable through its other name.
Can several things be removed on one line?
Yes, by separating the targets with commas: del a, b, stock["pears"] wipes all three. They are handled from left to right, and that is where care is needed. If the second one fails, the first is already gone and the third will never be reached.
Should variables be wiped at the end of a routine?
Rarely. Local variables go away by themselves when the function returns, and systematic use makes the code heavier to read. Two cases justify it: a very large object that should stop being held during a long run, and a reused name whose accidental re-reading must be ruled out.