[Tutor] Variable reference

Steven D'Aprano steve at pearwood.info
Wed Jul 8 02:54:24 CEST 2015


On Tue, Jul 07, 2015 at 06:50:25PM +0200, Peter Otten wrote:
[...]

> > Not so. The point of del being a statement is that it should be
> > considered an operation on the *reference*, not the *value* of the
> > reference. So:
> > 
> > x = 23
> > delete(x)  # if it existed, it would see the value 23
> > del x  # operates on the reference "x", not 23
> 
> Read again. I said that
> 
> del x
> 
> in the global namespace can be emulated with
> 
> globals().pop("x")
> 
> and that there is no equivalent to
> 
> del x
> 
> if x is a local/nonlocal name.

Yes, I read all that. There *could* (at least in theory) be an 
equivalent to `del x` using a function, which I called `delete("x")`, 
that applied to locals/nonlocals as well as globals. It would still not 
be *close* equivalent to del, because they operate in different spheres, 
semantically.

Using pop() is not quite right, because it doesn't just remove the 
key:value pair, it *returns the value*. So that's a another difference 
between popping a key from the globals, and del:

del x: 
- operates on the variable x, not the value of x
- unbinds that variable
- has no return result (is a statement, not an expression

globals().pop("x"):
- operates on a value, the string "x", not a variable
- conceptually, not an unbinding operation at all
- returns the value bound to the variable x

This makes a practical difference at the interactive interpreter: 
popping as two side effects, only one of which is intended:

py> a, b = 1, 2
py> del a
py> globals().pop("b")
2


It's unlikely to change, since the current semantics of globals() is 
documented as a language feature, but in principle at least a Python 
implementation might optimize the language by making globals() more like 
locals(), i.e. calling globals() returns a *copy* of the global 
namespace, not the namespace itself.

As I said, this is unlikely to change without a period of deprecation, 
but still, del is *intended* to unbind variables, pop is not. The fact 
that globals().pop also unbinds them is an accident of the way manages 
globals.

For all these reasons, if I saw 

    globals().pop("x")

in code I was reviewing, I would change it to `del x` without 
hesitation. Not withstanding the fact that we *can* replace del with pop 
as above, we shouldn't.


[...]
> I agree that if you think that explicitly unbinding a name is a useful 
> feature of the language a statement is the way to go. For me it's a feature 
> I hardly ever use and that I hardly ever find compelling in other people's 
> code.

I completely agree that del is rarely useful! But *rare* is not *never*.

There are two compelling cases for using del on names: explicitly 
managing the names in a namespace (i.e. to avoid "namespace pollution") 
and avoiding long-lived global references to objects which you no 
longer need.

I agree that using del on a local variable is probably unnecessary 
micro-management. I can't think of any scenario where you would want to 
manually del a local variable. If you did, that's possibly a sign that 
your function is too big.

When writing a script or application, name management is not a big deal. 
But in a library, temporary variables are pollution. They make it harder 
for the users of your library to tell what's part of the API and what 
isn't, and they make "from module import *" less useful. So if I have a 
temporary global variable, I may want to get rid of it once I'm finished 
with it. Here's a snippet from a library module I have, designed to be 
used with * imports:

    tmp = set(C0.keys()) & set(C1.keys())
    assert not tmp, 'duplicate control code acronyms: %s' % tmp
    # Special check for SCG abbreviated acronym.
    assert 'SGC' not in C0
    assert 'SGC' not in C1
    # Validate that the ^ and ESC codes are correct.
    for C in (C0, C1):
        for cc in C.values():
            assert cc.code == _code(cc.ordinal), 'failed check: %s' % cc
    del C, cc, tmp

Those three temporary variables, C, cc and tmp, would otherwise hang 
around forever, polluting the namespace and confusing my module's users. 
(My module's users, so far, is mostly me, but I'm easily confused.)


> I'd happily resort to
> 
> x = None
> 
> should the need arise to dereference a specific variable.

Ah, reading that makes me sad, because it looks like you are not getting 
the difference between a *variable* (a name in a namespace) and the
*value* of that variable.

`x = None` does not remove the variable from the namespace, it just 
binds it to None. So it is no substitute for the del statement.


> > It's a work-around for the fact that
> > Python doesn't have dedicated syntax to say "operate on the reference
> > foo" rather than the value of foo.
> 
> I think Danny's point was that you should not micromanage name bindings at 
> all. Then Alan added that del is useful for dicts etc. on which I replied 
> that a method would be sufficient for that.

Sure, Python *could* have used methods for deleting a key from a dict, 
or a slice from a list:

    mydict.delete(key)  # like del mydict[key]

    mylist.delete(1, 20, 3)  # like del mylist[1:20:3]

But note that introduces a source of API confusion: people may use 
mylist.remove(1) when they mean delete, or the other way around. Either 
way, that error is harder to make with the del statement: there is no 
reasonable way for a person to be confused about whether

    del mylist[3]

removes the 3rd item, or an item with the value 3. With:

    mylist = [3, 6, 9, 12]
    mylist.delete(3)

it isn't clear whether you end up with [3, 6, 9] or [6, 9, 12].

And when it comes to attributes, we shouldn't use getattr, setattr or 
delattr with constant arguments:

    # Yes
    y = x.spam
    x.spam = 23
    del x.spam

    # No
    y = getattr(x, "spam")
    setattr(x, "spam", 23)
    delattr(x, "spam")

I wouldn't hestitate to replace any of the second set with the version 
from the first set.



-- 
Steve


More information about the Tutor mailing list