0

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)
thorwhalen
  • 1,920
  • 14
  • 26

2 Answers2

2

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.

user2357112
  • 260,549
  • 28
  • 431
  • 505
1

You are writing your example code as though del were a function taking arguments and assuming that the argument a[2] has to be processed first via __getitem__(). But strictly speaking, del is a statement. That means the language parser can treat it in a special way – in other words, not necessarily like a function call.

We can use the dis package to get some hints about that. Note how the del operation gets expressed directly as the very specific DELETE_SUBSCR operation. It bypasses the BINARY_SUBSCR step used by the len example.

from dis import dis

def f(xs):
    del xs[2]

def g(xs):
    len(xs[2])

print('\n# del')
dis(f)
print('\n# len')
dis(g)

Output (summarized):

# del
0 LOAD_FAST                0 (xs)
2 LOAD_CONST               1 (2)
4 DELETE_SUBSCR
6 LOAD_CONST               0 (None)
8 RETURN_VALUE

# len
 0 LOAD_GLOBAL              0 (len)
 2 LOAD_FAST                0 (xs)
 4 LOAD_CONST               1 (2)
 6 BINARY_SUBSCR
 8 CALL_FUNCTION            1
10 POP_TOP
12 LOAD_CONST               0 (None)
14 RETURN_VALUE
FMc
  • 41,963
  • 13
  • 79
  • 132