Python del vs assigning to None
Python's runtime handles memory for you, but every so often you'll see code that tries to give it a nudge. The two ways that come up are:
1x = Noneand
1del xThey look interchangeable. They aren't.
What each one does
x = None rebinds the name x to None. Whatever it pointed at before now has one fewer reference; if that was the last one, CPython's reference counter frees the object immediately. The name x is still there — using it gives you None.
del x removes the binding entirely. The name is gone. Touching x afterward raises NameError (or AttributeError if you del obj.attr on an instance attribute). The previously-referenced object also loses a reference and gets freed in the same moment if the count hits zero.
So: = None keeps the name with a tiny None reference; del removes the name as well.
Worth being precise about a couple of things, since the framing on this trips people up:
- Neither operation "tells the garbage collector" anything. CPython's main memory management is reference counting, not the cyclic GC. The cyclic collector only runs to break reference cycles; for plain refcount-to-zero, freeing is synchronous.
- Even when an object is freed, CPython's small-object allocator keeps the arena around. Memory rarely returns to the OS until the process exits.
Demonstration
1import sys
2
3x = 'Some text here to give the variable a decent size'
4y = 2
5
6print('x size before:', sys.getsizeof(x), 'bytes')
7
8x = None
9del y
10
11print('x value after:', x)
12print('x size after :', sys.getsizeof(x), 'bytes')
13print('x type after :', type(x).__name__)
14
15if x is None:
16 print('x is still bound, just to None')
17
18print('y after del:', y) # NameErrorOutput on CPython 3.12:
x size before: 90 bytes
x value after: None
x size after : 16 bytes
x type after : NoneType
x is still bound, just to None
Traceback (most recent call last):
File "demo.py", line 17, in <module>
print('y after del:', y)
NameError: name 'y' is not defined
A None-bound name carries the 16-byte cost of pointing at the singleton None (which is a value of type NoneType, not a type itself). A deleted name carries no cost — the local slot is empty.
The bytecode
1import dis
2
3def using_none():
4 x = 1
5 x = None
6
7def using_del():
8 x = 1
9 del x
10
11dis.dis(using_none)
12print()
13dis.dis(using_del)On CPython 3.12:
Assigning to None:
0 RESUME 0
2 LOAD_CONST 1 (1)
4 STORE_FAST 0 (x)
6 LOAD_CONST 0 (None)
8 STORE_FAST 0 (x)
10 RETURN_CONST 0 (None)
Using del:
0 RESUME 0
2 LOAD_CONST 1 (1)
4 STORE_FAST 0 (x)
6 DELETE_FAST 0 (x)
8 RETURN_CONST 0 (None)
del x is a single DELETE_FAST instruction. x = None is a LOAD_CONST None followed by a STORE_FAST — same shape as any other assignment. (The RESUME 0 at the top of each function is new in Python 3.11; it's the interpreter's reentry point.)
When you'd actually reach for either
Most of the time you don't need to. The cases where it earns its keep:
- Dropping a large local before a long-running call inside the same function, so the memory can be reused
- Releasing a reference held by a class attribute or module-level cache
- Breaking a reference cycle so the cyclic collector can clean it up sooner
For everything else, let the runtime do its job. Reach for del when you genuinely want the name gone; reach for = None when you want the name there but the data not.