Search code examples
pythonmagic-methodspython-collections

How does the python del function work without calling __getitem__


In python, an object is subscriptable when it's class defines a

__getitem__(self, k)

method, allowing us to "get items" through the bracket syntax:

obj[k]

The syntax for the builtin del function is:

del(obj[k])

This deletes item k from (subscriptable) object obj. But apparently, without calling the special getitem method.

See example below

>>> class A:
...     def __init__(self, d):
...         self._d = d
...
...     def __getitem__(self, k):
...         print("Calling __getitem__")
...         return self._d[k]
...
...     def __delitem__(self, k):
...         del(self._d[k])
...
>>>
>>> a = A({1: 'one', 2: 'two', 3: 'three'})
>>> a._d
{1: 'one', 2: 'two', 3: 'three'}
>>> a[2]
Calling __getitem__
'two'
>>> del(a[2])
>>> # Note there was no "Calling __getitem__" print!

So it seems like before a[2] forwards the work to the getitem method, the interpreter is aware of the del context, and bypasses it, calling directly

a.__delitem__(2)

instead.

How does that work?

And most of all: Is this mechanism customizable?

Could I for example, write a function foo so that

foo(obj[k])

doesn't ever call

obj.__getitem__(k)

but instead, for example,

obj.foo(k)

Solution

  • del is not a function. It can do this because it's not a function. This is why it's not a function. It's a keyword built into the language as part of the del statement.

    To keep in mind that things like del and return aren't functions (and avoid unexpected precedence surprises), it's best to not put parentheses around the "argument":

    del whatever
    

    rather than

    del(whatever)
    

    del does not take an object and delete it. The thing to the right of del is not an expression to be evaluated. It is a target_list, the same kind of syntax that appears on the left side of the = in an assignment statement:

    target_list     ::=  target ("," target)* [","]
    target          ::=  identifier
                         | "(" [target_list] ")"
                         | "[" [target_list] "]"
                         | attributeref
                         | subscription
                         | slicing
                         | "*" target
    

    To delete a subscription target like obj[k], Python evaluates the expression obj and the expression k to produce two objects, then calls the __delitem__ method of the first object with the second object as the argument. obj[k] is never evaluated as an expression, though pieces of it are.


    This all relies on compiler and grammar support, and cannot be done for arbitrary user-defined functions.