Death to tuples!

Bengt Richter bokr at oz.net
Thu Dec 1 23:42:34 EST 2005


On 1 Dec 2005 09:24:30 GMT, Antoon Pardon <apardon at forel.vub.ac.be> wrote:

>On 2005-11-30, Duncan Booth <duncan.booth at invalid.invalid> wrote:
>> Antoon Pardon wrote:
>>
>>>> The left one is equivalent to:
>>>>
>>>> __anon = []
>>>> def Foo(l):
>>>>    ...
>>>>
>>>> Foo(__anon)
>>>> Foo(__anon)
>>> 
>>> So, why shouldn't: 
>>> 
>>>    res = []
>>>    for i in range(10):
>>>       res.append(i*i)
>>> 
>>> be equivallent to:
>>> 
>>>   __anon = list()
>>>   ...
>>> 
>>>    res = __anon
>>>    for i in range(10):
>>>       res.append(i*i)
>>
>> Because the empty list expression '[]' is evaluated when the expression 
>> containing it is executed.
>
>This doesn't follow. It is not because this is how it is now, that that
>is the way it should be.
>
>I think one could argue that since '[]' is normally evaluated when
>the expression containing it is excuted, it should also be executed
>when a function is called, where '[]' is contained in the expression
 ^^^^^^^^^^^^^^^^^^^^^^^[1] ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>determining the default value.
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^[2]
Ok, but "[]" (without the quotes) is just one possible expression, so
presumably you have to follow your rules for all default value expressions.
Plain [] evaluates to a fresh new empty list whenever it is evaluated,
but that's independent of scope. An expression in general may depend on
names that have to be looked up, which requires not only a place to look
for them, but also persistence of the name bindings. so def foo(arg=PI*func(x)): ...
means that at call-time you would have to find 'PI', 'func', and 'x' somewhere.
Where & how?
1) If they should be re-evaluated in the enclosing scope, as default arg expressions
are now, you can just write foo(PI*func(x)) as your call. So you would be asking
for foo() to be an abbreviation of that. Which would give you a fresh list if
foo was defined def foo(arg=[]): ...

Of course, if you wanted just the expression value as now at def time, you could write
def foo(...):...; foo.__default0=PI*fun(x) and later call foo(foo.__default0), which is
what foo() effectively does now.

2) Or did you want the def code to look up the bindings at def time and save them
in, say, a tuple __deftup0=(PI, func, x) that captures the def-time bindings in the scope
enclosing the def, so that when foo is called, it can do arg = _deftup0[0]*_deftup0[1](_deftup0[2])
to initialize arg and maybe trigger some side effects at call time.

3) Or did you want to save the names themselves, __default0_names=('PI', 'func', 'x')
and look them up at foo call time, which is tricky as things are now, but could be done?

>
>>>> The left has one list created outside the body of the function, the
>>>> right one has two lists created outside the body of the function. Why
>>>> on earth should these be the same?
>>> 
>>> Why on earth should it be the same list, when a function is called
>>> and is provided with a list as a default argument?
It's not "provided with a list" -- it's provided with a _reference_ to a list.
You know this by now, I think. Do you want clone-object-on-new-reference semantics?
A sort of indirect value semantics? If you do, and you think that ought to be
default semantics, you don't want Python. OTOH, if you want a specific effect,
why not look for a way to do it either within python, or as a graceful syntactic
enhancement to python? E.g., def foo(arg{expr}):... could mean evaluate arg as you would like.
Now the ball is in your court to define "as you would like" (exactly and precisely ;-)


>>
>> Because the empty list expression '[]' is evaluated when the 
>> expression containing it is executed.
>
>Again you are just stating the specific choice python has made.
>Not why they made this choice.
Why are you interested in the answer to this question? ;-) Do you want
to write an accurate historical account, or are you expressing discomfort
from having had to revise your mental model of other programming languages
to fit Python? Or do you want to try to enhance Python in some way?
>
>>> I see no reason why your and my question should be answered
>>> differently. 
>>
>> We are agreed on that, the answers should be the same, and indeed they are. 
>> In each case the list is created when the expression (an assigment or a 
>> function definition) is executed. The behaviour, as it currently is, is 
>> entirely self-consistent.
>
>> I think perhaps you are confusing the execution of the function body with 
>> the execution of the function definition. They are quite distinct: the 
>> function definition evaluates any default arguments and creates a new 
>> function object binding the code with the default arguments and any scoped 
>> variables the function may have.
>
>I know what happens, I would like to know, why they made this choice.
>One could argue that the expression for the default argument belongs
>to the code for the function and thus should be executed at call time.
>Not at definion time. Just as other expressions in the function are
>not evaluated at definition time.
Maybe it was just easier, and worked very well, and no one showed a need
for doing it differently that couldn't easily be handled. If you want
an expression evaluated at call time, why don't you write it at the top
of the function body instead of lobbying for a change to the default arg
semantics? The answer could be a scoping problem, I suppose. Is there
something you'd like that couldn't be handled with (an efficent sugary version of)

    sentinel = object()
    def foo(arg=(sentinel,lambda:expr)):
        if type(arg) is tuple and len(arg)==2 and arg[0] is sentinel: arg = arg[0]()
        ...

or would the expression evaluation maybe not suit once beyond expr being just []?
I'm trying to move off "why" onto "what" ;-)

>
>So when these kind of expression are evaluated at definition time,
>I don't see what would be so problematic when other functions are
>evaluated at definition time to.
>
>> If the system tried to delay the evaluation until the function was called 
>> you would get surprising results as variables referenced in the default 
>> argument expressions could have changed their values.
>
>This would be no more surprising than a variable referenced in a normal
>expression to have changed values between two evaluations.
Sure, you could have it work that way, but would it really be useful?

Is this a matter of thinking up some sugar for

    def foo(arg=None)
        if arg is None: arg = []

or what are we pursuing?
Hm, I was just going to say it might be nice to have a builtin standard sentinel,
or a convention for using something as such. I don't really like manufacturing
sentinel=object() when I need something other than None. So it just occurred to me
maybe

    def foo(arg=NotImplemented)
        if arg is NotImplemented: arg = []

maybe SENTINEL could be defined similarly as a builtin constant.

Regards,
Bengt Richter



More information about the Python-list mailing list