why del is not a function or method?

Steve D'Aprano steve+python at pearwood.info
Tue Oct 17 01:07:15 EDT 2017


On Tue, 17 Oct 2017 02:40 pm, Ben Finney wrote:

> Steve D'Aprano <steve+python at pearwood.info> writes:

>> `del` cannot be a method or a function, because the argument to `del`
>> is the name of the variable, not the contents of the variable.
> 
> Since a Python “variable” does not have contents, this is IMO another
> instance where using the term “variable” is not helpful.
> 
>     >>> x = 123
>     >>> y = [0, x, 2, x, 4]
> 
> Neither of the names ‘x’ nor ‘y’ have content; 

What I meant by "content" was "value". In hindsight, I don't even know why I
wrote the word "content". It was not a good choice of words. Mea culpa.

Of course the variables "x" and "y" have values: the value of x is 123, and
the value of y is the specific list bound to the name "y" in the appropriate
namespace. If you don't agree with that, then our terminology is so far apart
that I don't believe we can ever agree, or understand each other.

The point is, if del were a function, then calling del(x) would pass the
*value* of x into the function, not the name 'x' -- unless the interpreter
made del a special case, not a regular function.

But if the interpreter did that, why bother with function notation and the
extraneous parentheses? Why not just make it a statement and avoid surprising
the programmer by giving the del function super-powers that no other function
has?

Which, of course, is exactly what Guido did.


> they are references to objects.

You are conflating the implementation details of how the Python VM implements
name binding (a mapping between names and references to objects) and the
high-level semantics of Python code. In CPython, at least, such references
are pointers. They're something analogous in Jython and IronPython.

It is sometimes useful to peer under the hood and talk about the
implementation of the VM, but I don't think this is one of those times. There
are too many inconsistencies in how we need to use the word "reference" to
make this strategy viable -- see below.


[...]
> So when we give an argument to ‘del’, that argument is not always a
> “variable”; it is always a reference.

The argument to del is a comma-separated list of l-values, that is, the kind
of expression which is legal on the left-hand side of an assignment
statement. That includes:

- bare names like `x`

- qualified names like `module.x` or `instance.attribute`

- items in mappings like `mydict[key]`

- items in sequences like `mylist[0]`

- lists and tuples of such l-values, e.g. this is legal:

    py> a, b, c, d = 1, 2, 3, 4
    py> del a, [b, c], d


as well as more complex examples. But you can't pass an arbitrary "reference"
to del and expect something sensible:

    del math.sin(1)

would, if it were legal, evaluate the expression math.sin(1) and return the
float 0.8414... (or, if you prefer to talk about the *implementation*, return
a reference to the float object) and then try to delete it in some sense. But
that's not legal, and you get a syntax error at compile time.

If del accepted references in the usual sense, this would be legal but
pointless: del would receive the reference to the float object, and delete
the reference, leaving the float to possibly be garbage collected. But that's
not what del does.


> We can delete one item from a list, because that item is accessed via a
> reference; we give the same reference as argument to ‘del’::
> 
>     >>> del y[1]

In context, I was specifically referring to unqualified names because deleting
list items *can* be done with a method call. In fact, the above line ends up
calling y.__delitem__[1]. In an alternative universe, Guido might have
forgone the `del` statement and required us to call:

    list.pop(1)

or similar for dicts and other collections. The OP was already thinking along
those lines when he or she asked the question, so I didn't think I needed to
cover that case.

But the reason del is a statement is that it *also* covers cases where we
cannot use a regular method or function, and the simplest such case is
unbinding a name.

Now, to go back to your comment that 

    "we give the same reference as argument to ‘del’"

that's incorrect, or at least, in order for it to be correct we must use *two*
different meanings to the word "reference", depending on the context. If I
write:

    y = [None, "Aardvark", None, None]
    print(y[1])  # (A)
    del y[1]  # (B)

then the same descriptive text:

    "the reference y[1]"

needs to be understood in two different ways:

- in line (A) y[1] is a reference to the string object "Aardvark";

- in line (B) y[1] is *not* a reference to the string object, but
  stands for the 1st position in the sequence referred to by `y`.


So it is misleading, or at least surprising, to use the term "reference" in
both situations. It isn't clear whether you mean reference to the value or
reference or not.

In logic and philosophy, we often need to think about the distinction between
*using* a word and *mentioning* the word:

https://en.wikipedia.org/wiki/Use%E2%80%93mention_distinction

Failing to distinguish the two leads to fallacies:

https://www.logicallyfallacious.com/tools/lp/Bo/LogicalFallacies/180/Use-Mention-Error

some of which are pretty obvious ("Python is not a programming language, it is
a six-letter word"), and others not so obvious.

Python has an analogous distinction:

(1) Python's evaluation of an expression is normally analogous to *using* a
word. For example:

    print(spam.eggs)

the interpreter evaluates the expression `spam.eggs`, returning an object of
some kind (often called an "r-value", or just a value), then passes the
object to print, which prints it.

(2) But binding operations (including unbinding) are analogous to *mentioning*
an expression (with the condition that only certain kinds of expressions are
legal as assignment targets). For example:

    del spam.eggs

does not evaluate the expression `spam.eggs` and pass the resulting object to
del. That's why del cannot be a function: it needs to operate on its
arguments before the interpreter evaluates them to objects.

Of course, there is a way that we could make del a regular function, even for
unqualified names[2]: we could quote the arguments ourselves. So to delete
the bare name "x" from the global scope, we could write:

    delete("x", globals())

To delete an attribute or qualified name, we could write:

    delete("attribute", instance)  # or use delattr(instance, "attribute")

    delete("x", module)

To delete an item from a list:

    delete(1, mylist)  # or use mylist.pop(1)

and so forth. All pretty clumsy and ugly, but it could work. So the ultimate
reason why del is a statement rather than a function is that Guido wanted to
be able to unbind r-values without having to quote them first.








[1] Except in the loosest sense that any use of words to refer to a concept is
a reference, e.g. the words "Queen Elizabeth" is a reference to the current
monarch of the UK, Elizabeth Windsor.


[2] Almost. It would be difficult or impossible to delete a local (but why
would you want to?), a nonlocal, or a class-level name binding from inside
the class. E.g.:

class K:
    x = 1
    del x


-- 
Steve
“Cheer up,” they said, “things could be worse.” So I cheered up, and sure
enough, things got worse.




More information about the Python-list mailing list