Values and objects

Steven D'Aprano steve+comp.lang.python at pearwood.info
Sun May 11 01:11:56 EDT 2014


On Sun, 11 May 2014 13:30:03 +1000, Chris Angelico wrote:

> On Sun, May 11, 2014 at 1:17 PM, Steven D'Aprano
> <steve+comp.lang.python at pearwood.info> wrote:
>> But that is an implementation detail. IronPython and Jython use an
>> ordinary dict for local variable namespaces, just like globals.
>> Consider this example from Jython:
>>
>>>>> spam = 9999
>>>>> def modify(namespace):
>> ...     namespace['spam'] = 42
>> ...
>>>>> def demo():
>> ...     modify(locals())
>> ...     spam = spam
>> ...     return spam
>> ...
>>>>> demo()
>> 42
> 
> All you're proving here is that, in some Pythons, locals() is writeable.
> What happens if you remove the "spam = spam" line? Would demo() not then
> return spam from the global scope, 

Yes, this. See my previous email, and take careful note of Rule #2: in 
the absence of a binding operation, variables are not treated as local.

> because the variable does not exist at local scope? 

No to this. Consider this example:


>>> spam = 9999
>>> def modify(namespace):
...     namespace['spam'] = 42
...
>>> def demo2():
...     assert 'spam' not in locals()
...     modify(locals())
...     assert 'spam' in locals()
...     return spam
...
>>> demo2()
9999


This proves that the spam variable *does* exist in locals, but it is not 
seen because the "return spam" doesn't check the local scope, so it sees 
the global spam.

Sadly, the version of Jython I have doesn't provide a working dis module, 
but if it did I expect it would show the equivalent of what CPython does: 
the first version uses LOAD_FAST to look up "spam", and the second 
version used LOAD_GLOBAL (a misnomer since it doesn't *just* look up 
globals).


> Every example you've shown is simply giving a value to
> something that you've created by the normal method of having an
> assignment inside the function.

Nonsense. Look at the original examples again, more closely. Here they 
are again, this time with comments:

def test():
    if False: spam = None  # Dead code, never executed.
    d = locals()
    d['spam'] = 23  # Not a normal assignment.
    return spam

def test():
    locals()['spam'] = 42  # Not a normal assignment.
    return spam
    spam = None  # Dead code.


and from my reply to Devin:

def modify(namespace):
    namespace['spam'] = 42

def demo():
    modify(locals())  # Not an ordinary assignment.
    spam = spam  # Where does the initial value of spam come from?
    return spam


The *only* purpose of the dead code in the two test() functions is to 
force the compiler to use LOAD_FAST (or equivalent) rather than 
LOAD_GLOBAL. But they aren't ever executed. At no time do I use normal 
name binding assignment to create local variables. In the demo() 
function, if I expand the line "spam = spam" out:

    temp = spam  # Look up of spam occurs first.
    spam = temp  # Binding occurs second.

the difficulty should be even more obvious. Where does the value of spam 
come from before the binding?

The answer is that it comes from writing directly to the namespace (a 
dict). There is no fixed slot for spam waiting for a value, because 
Jython and IronPython don't use fixed slots. There's only a dict.

If we were using globals, it would be be more obvious what was going on, 
and there would be no argument about whether or not a variable exists 
before you give it a value. The answer would be, of course it doesn't 
exist before it has a value. Python declarations (whether implicit or 
explicit) don't create variables, they just instruct the compiler how to 
perform lookups on them.

# Global scope
print spam  # fails, because there is no spam variable yet
"spam" in globals()  # returns False
globals()['spam'] = 42  # now it exists
spam = spam  # a no-op
print spam


It's only because CPython is special, and locals() is special, that the 
equivalent code is indeterminate inside functions.



-- 
Steven D'Aprano
http://import-that.dreamwidth.org/



More information about the Python-list mailing list