On Wed, May 27, 2020 at 05:03:09AM +1000, Chris Angelico wrote:
def foo(): if False: x = 0 # what is x now?
There is no *value* in x, yet x has a state.
In Python code, no, it has no state, it's just an unbound name. That's literally a name that has nothing bound to it, hence no state. In the CPython 3 implementation, it has a hidden state: there's a fixed array representing the locals, one of those array slots represents x, and there is some kind of C-level special state to distinguish between "this slot is filled" and "this slot is not filled". But that's purely an optimization. Locals can also be backed by a dict, like globals. That is what happens in Jython, so when you call locals() you get back the actual local namespace dict and modifications to the variables works. (Unlike in CPython.) # Jython 2.7 >>> def test(): ... locals()['x'] = 999 ... if False: ... # Fool the compiler into treating x as a local. ... x = None ... print(x) ... >>> test() 999 IronPython appears to be different yet again, but I don't understand what it is doing so I can't explain it. In CPython 2, some locals were backed by the fast array slots, some were not. I think (but I'm not sure!) that if the compiler saw an exec or a star import inside a function, it switched off the fast array optimization. Or something like that. The bottom line here is that the Python execution model has names. Names can be bound to a value (an object), or they can be unbound in which case they have no state *in Python*. Whether that unboundness is represented by a nil pointer or a special magic value or is a consequence of a key being missing from a namespace dict is part of the implementation, not part of the Python semantics.
So we could have some kind of definition of optional parameters where, rather than receiving a default, they would simply not be bound.
We could have an `if undef` keyword :-) if undef x: x = something (Not entirely serious about this proposal.)
def foo(?x): # what is x?
There would want to be a new way to query this state, though, because I think this code is ugly enough to die:
def foo(?x): try: x except UnboundLocalError: ... # do this if x wasn't passed in else: ... # do this if we have a value for x
There's always: if 'x' in locals()
(It's probably best to define this ONLY for local variables. Module or class name bindings behave differently, so they would simply never be in this unbound state.
Of course they are! # Module level state. x = None; del x # Ensure x is unbound. print(x) I frequently write top-level module code that tests for the existence of a global or builtin: try: spam except NameError: def spam(): ... That could become: if undef spam: def spam(): ... I would be cross if `if undef` worked inside functions but not globally. -- Steven