From: Steven D'Aprano on
On Mon, 12 Jul 2010 20:28:49 +0200, Alf P. Steinbach /Usenet wrote:

> As I see it it doesn't matter whether the implementation is CPython call
> frame slots or that mechanism called something else or a different
> mechanism called the same or a different mechanism called something
> different; what IMO matters is same semantics, that any assignment to a
> variable within a routine serves as a compile time declaration, creating
> that local variable in advance, unless, with Python 3.x., that name has
> been declared as a 'global' or 'nonlocal'.
>
> So, this is a possible point of disagreement.
>
> I say the semantics of local variable creation are part of the language
> definition, but I get the /impression/ that maybe you think it's
> CPython-specific, that e.g.
>
> def foo():
> x
> x = 0
>
> might not raise an unassigned variable exception with some conforming
> Python implementation, i.e. different effect for same code with
> different implementations, that this is at least /unspecified behavior/
> in Python?

Almost.

I believe that "any assignment to a variable within a routine serves as a
compile time declaration" is a promise of the language, but what an
implementation does in response to that declaration is unspecified. So
long as foo() raises NameError, or a subclass of it, it will be a
conforming Python implementation.

That's what Python 1.5 does, and I think if some competing implementation
targeted 1.5 we'd still be happy to call it Python. (Although we might
wonder about the author's sanity...) Implementations are free to subclass
NameError, as CPython does with UnboundLocalError, but mustn't raise a
completely unrelated error, or no error at all. E.g. I don't think it
would be acceptable to implicitly create new names and bind them to some
arbitrary default value.

E.g. an implementation might do something like this:

* when parsing the function, prior to compiling the byte-code, tag
every name with a sigil representing whether it is local or non-local;
* compile a single byte-code for name lookup;
* when executing that instruction, if the name is tagged as a local,
search only the local namespace, otherwise search the nonlocal,
global and builtin namespaces;
* when displaying names to the user (say, in tracebacks) suppress
the sigil.

Under this implementation, no variable actually exists until it is
assigned to.

This is equivalent to shifting the decision to use LOAD_FAST or
LOAD_GLOBAL to runtime rather than compile time, so it would probably
hurt performance rather than increase it, but it would still be a
conforming implementation.

But of course I'm not Guido, and he has the final word on what counts as
acceptable behaviour.



--
Steven
From: Steven D'Aprano on
On Mon, 12 Jul 2010 22:57:10 +0200, Alf P. Steinbach /Usenet wrote:

> Existence of a variable means, among other things, that
>
> * You can use the value, with guaranteed effect (either unassigned
> exception
> or you get a proper value): in particular, you won't be accessing a
> global if you're using the name of a local declared by a later
> assignment.

That is too strong. Given the global code:

x

(where x doesn't exist in the global namespace, and therefore does not
exist, as you agreed earlier) Python promises to raise NameError. By the
above definition, this counts as "variable x exists".

But surely that is undesirable -- that implies that *all* variables
exist. Even $%@*@( is a variable that exists, as that is guaranteed to
raise SyntaxError.



--
Steven
From: Alf P. Steinbach /Usenet on
* Steven D'Aprano, on 13.07.2010 01:50:
> On Mon, 12 Jul 2010 22:57:10 +0200, Alf P. Steinbach /Usenet wrote:
>
>> Existence of a variable means, among other things, that
>>
>> * You can use the value, with guaranteed effect (either unassigned
>> exception
>> or you get a proper value): in particular, you won't be accessing a
>> global if you're using the name of a local declared by a later
>> assignment.
>
> That is too strong. Given the global code:
>
> x
>
> (where x doesn't exist in the global namespace, and therefore does not
> exist, as you agreed earlier) Python promises to raise NameError. By the
> above definition, this counts as "variable x exists".
>
> But surely that is undesirable -- that implies that *all* variables
> exist. Even $%@*@( is a variable that exists, as that is guaranteed to
> raise SyntaxError.

Hm, I already answered someone else here committing that logic error.

In one case an exception is generated by removing a variable.

In the other case an exception is generated by adding a variable.


Cheers & hth.,

- Alf

--
blog at <url: http://alfps.wordpress.com>
From: Alf P. Steinbach /Usenet on
* Steven D'Aprano, on 13.07.2010 01:34:
> On Mon, 12 Jul 2010 20:28:49 +0200, Alf P. Steinbach /Usenet wrote:
>
>> As I see it it doesn't matter whether the implementation is CPython call
>> frame slots or that mechanism called something else or a different
>> mechanism called the same or a different mechanism called something
>> different; what IMO matters is same semantics, that any assignment to a
>> variable within a routine serves as a compile time declaration, creating
>> that local variable in advance, unless, with Python 3.x., that name has
>> been declared as a 'global' or 'nonlocal'.
>>
>> So, this is a possible point of disagreement.
>>
>> I say the semantics of local variable creation are part of the language
>> definition, but I get the /impression/ that maybe you think it's
>> CPython-specific, that e.g.
>>
>> def foo():
>> x
>> x = 0
>>
>> might not raise an unassigned variable exception with some conforming
>> Python implementation, i.e. different effect for same code with
>> different implementations, that this is at least /unspecified behavior/
>> in Python?
>
> Almost.
>
> I believe that "any assignment to a variable within a routine serves as a
> compile time declaration" is a promise of the language, but what an
> implementation does in response to that declaration is unspecified. So
> long as foo() raises NameError, or a subclass of it, it will be a
> conforming Python implementation.
>
> That's what Python 1.5 does, and I think if some competing implementation
> targeted 1.5 we'd still be happy to call it Python. (Although we might
> wonder about the author's sanity...) Implementations are free to subclass
> NameError, as CPython does with UnboundLocalError, but mustn't raise a
> completely unrelated error, or no error at all. E.g. I don't think it
> would be acceptable to implicitly create new names and bind them to some
> arbitrary default value.
>
> E.g. an implementation might do something like this:
>
> * when parsing the function, prior to compiling the byte-code, tag
> every name with a sigil representing whether it is local or non-local;
> * compile a single byte-code for name lookup;
> * when executing that instruction, if the name is tagged as a local,
> search only the local namespace, otherwise search the nonlocal,
> global and builtin namespaces;
> * when displaying names to the user (say, in tracebacks) suppress
> the sigil.
>
> Under this implementation, no variable actually exists until it is
> assigned to.
>
> This is equivalent to shifting the decision to use LOAD_FAST or
> LOAD_GLOBAL to runtime rather than compile time, so it would probably
> hurt performance rather than increase it, but it would still be a
> conforming implementation.
>
> But of course I'm not Guido, and he has the final word on what counts as
> acceptable behaviour.

The 3.1.1 docs explicitly require UnboundLocalError (I just checked).

So, at least for 3.x it is not an implementation detail.

Anyway, your phrase "actually exist" presumably refers to storage allocation.
That is an implementation detail. As a similar implementation scheme, a compiler
can in certain cases detect that a variable is only assigned once, and
substitute the value whereever that variable is used, not allocating any storage
for it. This is the case in Python, in C++ and in almost any language. We don't
start doubting the existence of variables in general (as some in the Python
community do) on such grounds. More to the point of this sub-thread, it would be
impossible to reason about things if one had to take into account the particular
implementation's details at all times. Doing that renders most terms, including
"exist", pretty meaningless and useless, since you would have to check whether
each particular variable was optimized away or not: for the purpose of
discussing existence in Python, what matters is portable effect.

Summing up, how CPython implements the required semantics, is irrelevant.

:-)


Cheers from Norway,

- Alf

--
blog at <url: http://alfps.wordpress.com>
From: Rami Chowdhury on

On Jul 12, 2010, at 15:55 , Alf P. Steinbach /Usenet wrote:
> * Rami Chowdhury, on 13.07.2010 00:14:
>> Perhaps I'm misunderstanding, but ...
>>
>> On Jul 12, 2010, at 13:57 , Alf P. Steinbach /Usenet wrote:
>>>
>>> Existence of a variable means, among other things, that
>>>
>>> * You can use the value, with guaranteed effect (either unassigned exception
>>> or you get a proper value)
>>
>> Surely by that definition any variable in any Python program "exists" -- you
>> are guaranteed to get one of NameError, UnboundLocalError, or a value. That
>> seems to argue away the meaning of the word entirely, and renders it not
>> particularly useful.
>
> No, you're conflating non-existence (NameError) with accessing the value of an existing but unassigned variable (UnboundLocalError). It is like arguing that vacuum is the same as cement because sticking your head into it for ten minutes or so yields an effect -- you're dead -- that in many ways is just about the same.

Right -- but you're playing with the definition of "existence" there, and since you're not using it quite consistently, I wasn't sure what you meant. Your discussion of the language reference below helps clear it up.

>
>>> How the Python implementation implements that is an implementation detail.
>>>
>>> In short, how CPython does things is completely irrelevant to the language's
>> semantics, so you're conflating things here.
>>>
>>
>> As I'd understood the previous discussion, it is the CPython implementation
>> that reserves local names and produces UnboundLocalErrors. The language
>> semantics don't call for it, and another implementation might choose to handle
>> function locals the same way as globals, through a namespace dictionary -- in
>> which case the variable *wouldn't* exist in any way, shape, or form until it
>> was assigned to.
>>
>> What am I getting wrong here?
>
> The bit about the language semantics not specifying the effect.
>
> From the 3.1.1 language reference ยง4.1:
>
> "When a name is not found at all, a NameError exception is raised. If the name refers to a local variable that has not been bound, a UnboundLocalError exception is raised. UnboundLocalError is a subclass of NameError."
>
> And it goes on to elaborate on that, a little later:
>
> "If a name binding operation occurs anywhere within a code block, all uses of the name within the block are treated as references to the current block. This can lead to errors when a name is used within a block before it is bound. This rule is subtle. Python lacks declarations and allows name binding operations to occur anywhere within a code block. The local variables of a code block can be determined by scanning the entire text of the block for name binding operations."

Ah, interesting. Thank you for pointing that out -- I didn't know that. I notice the same paragraph is in the 2.7 reference, so it presumably holds for 2.x implementations as well.

> This is the usual reaction of the religious when exposed to reality.
>
> Cheers & hth.

It does, thank you. Although the repeated references to the "religious" and their "refusal to accept reality" are less than helpful.

>
> --
> blog at <url: http://alfps.wordpress.com>
> --
> http://mail.python.org/mailman/listinfo/python-list