[Python-ideas] proto-PEP: Fixing Non-constant Default Arguments
Jan Kanis
jan.kanis at phil.uu.nl
Tue Jan 30 12:21:10 CET 2007
On Tue, 30 Jan 2007 08:09:37 +0100, Chris Rebert <cvrebert at gmail.com>
wrote:
> I also reworded the sentences using 'shall'. I don't really get why that
> matters, but what the heck.
Well, it doesn't. Just makes it nicer to read. (As long as the text is
still clear, that is.)
> Jan Kanis wrote:
>> Well, I (obviously) like the idea, but your pep misses some important
>> points (and some not so important).
>> The important one is about the scoping of variables in default
>> expressions. The pep says nothing about them.
>> If I read the pep correctly, any variables in default expressions are
>> handled the same as variables in the body of a function. This means the
>> compiler decides if they should be local, lexical or global. If there
>> is an assignment to a variable, the compiler makes it a local, else it
>> finds the right enclosing scope (lexical or global). In the current
>> python, this works fine:
>> >>> a = 123
>> >>> def foo(b=a):
>> a = 2
>> print a, b
>> >>> foo()
>> 2 123
>> >>> a = 42
>> >>> foo()
>> 2 123
>> In the pep, the a in the default expression would be handled just like
>> any other a in the function body, which means it wil become a _local_
>> variable. Calling the function would then result in an
>> UnboundLocalError. Just like this in current python:
>> >>> a = 123
>> >>> def foo(b=None):
>> b = a if b==None else b
>> a = 2
>> print a
>> >>> foo()
>> Traceback (most recent call last):
>> File "<pyshell#22>", line 1, in <module>
>> foo()
>> File "<pyshell#21>", line 2, in foo
>> b = a if b==None else b
>> UnboundLocalError: local variable 'a' referenced before assignment
>> The solution, I think, as I wrote in my previous messages, is to have
>> the compiler explicitly make variables in default expressions lexical
>> or global variables.
>> This would still break the foo() in my example above, because you can't
>> assign to a lexical variable, or to a global that isn't declared
>> global. Therefore I think the compiler should distinguish between the a
>> in the default expression and the a in the function body, and treat
>> them as two different variables. You can think about it as if the
>> compiler silently renames one of them (without this rename being
>> visible to python code. AFAIK the bytecode is stack based and closure
>> vars are put in some kind of anonymous cell, which means they both
>> don't actually have a name anyway.)
Also, I've been thinking about the part that I suggested about having
python distinguish between lexically scoped variables in default
expressions and local variables that may have the same name.
To clarify my proposal, a demonstration:
Current python:
a = 4
b = 5
def foo(x = [b]*a):
x = copy.deepcopy(x) # one possible workaround to have the same list on
every call
b = 'bla' # no name clash of this b and b in default arg expr
x += [b]
print x
foo()
# prints [5, 5, 5, 5, 'bla']
foo()
# prints [5, 5, 5, 5, 'bla']
proposed semantics:
a = 4
b = 5
def foo(x = [b]*a):
# deepcopy() no longer nescessary
b = 'bla' # no name clash of this b and b in default arg expr, the
compiler distinguishes them
x += [b]
print x
foo()
# prints [5, 5, 5, 5, 'bla']
foo()
# prints [5, 5, 5, 5, 'bla']
This would work the same as this code in the current python. Imagine it as
the compiler silently transforming the above code to this code:
a = 4
b = 5
def foo(x = _undefined):
if x == _undefined:
x = [b]*a
_b = 'bla'
x += [_b]
print x
foo()
# prints [5, 5, 5, 5, 'bla']
foo()
# prints [5, 5, 5, 5, 'bla']
note: the use of deepcopy assumes that whatever is passed to foo is
deepcopyable. With the new semantics this function can be used without
these kind of assumptions.
However, I've been thinking about weather it is a good idea to have python
distinguish between the local and lexical 'b' in the above code, and do
the what you can think of as implicit renaming. Making the distinction
makes the whole proposal more backward compatible, however I think that if
backward compatibility were not an issue, it would be best not to make the
distinction and just have the programmer choose a different name for the
local variable. This would improve readability as there are no two
different variables with the same name in the same piece of code, and
decrease language complexity as there is no rule that's best explained in
terms of variable name rewriting.
Not making the distinction does cause the default arguments proposal to be
less backward compatible, but I don't know if that is really a big
problem. I assume this pep wil be a py3k pep anyway, so there are going to
be incompatibilities anyway. Also, the compiler is perfectly able to see
at compile time if a variable used in a default expression and as a local,
so it is able to raise a very descriptive error.
All in all, I think this part of my proposal is not such a good idea,
unless there's going to be some kind of transitional implementation of
this pep in py 2.x
meta-note: I hope it is clear what I'm trying to say. Basically, I'm
clarifying, arguing against, and retracting a part of my own proposal I
made in an earlier mail.
- Jan
More information about the Python-ideas
mailing list