Default parameters

Carl Banks imbosol at aerojockey.invalid
Sat Dec 20 12:55:25 EST 2003


Bengt Richter wrote:
> 
> 
> On Sat, 20 Dec 2003 03:55:22 GMT, Carl Banks <imbosol at aerojockey.invalid> wrote:
> 
>>Bengt Richter wrote:
>>> 
>>> 
>>> On Sat, 20 Dec 2003 01:43:00 GMT, Carl Banks <imbosol at aerojockey.invalid> wrote:
>>> 
>>>>Paul Rubin wrote:
>>>>> 
>>>>> 
>>>>> Carl Banks <imbosol at aerojockey.invalid> writes:
>>>>>> Consider something like this:
>>>>>> 
>>>>>>     def func(param=((1,2),(3,4),(5,6),(7,8))):
>>>>>>         whatever
>>>>>> 
>>>>>> Do you really want to be building a big-ass nested tuple every time
>>>>>> the function is called?
>>>>> 
>>>>> Come on, the compiler can easily recognize that that list is constant.
>>>>
>>>>Yes, but that doesn't account for all expensive parameters.  What
>>>>about this:
>>>>
>>>>    DEFAULT_LIST = ((1,2),(3,4),(5,6),(7,8))
>>>>
>>>>    def func(param=DEFAULT_LIST):
>>>>        pass
>>>>
>>>>Or this:
>>>>
>>>>    import external_module
>>>>
>>>>    def func(param=external_modules.create_constant_object()):
>>>>        pass
>>>>
>>>>Or how about this:
>>>>
>>>>    def func(param={'1': 'A', '2': 'B', '3': 'C', '4': 'D'}):
>>>>        pass
>>>>
>>>>
>>>>The compiler couldn't optimize any of the above cases.
>>> 
>>> For the DEFAULT_LIST (tuple?) and that particular dict literal, why not?
>>
>>
>>Well, the value of DEFAULT_LIST is not known a compile time (unless, I
>>suppose, this happens to be in the main module or command prompt).
>>The literal is not a constant, so the compiler couldn't optimize this.
> 
> Well, according to the argument, we would be dealing with an
> optimizing compiler, so presumably the compiler would see a name
> DEFAULT_LIST and simply compile a call-time binding of param to
> whatever DEFAULT_LIST was bound to, and not bother further. It could
> notice that the DEFAULT_LIST binding was still undisturbed, and that
> it was to an immutable tuple with no mutable elements, which ISTM is
> effectively a constant, but that analysis would be irrelevant, since
> the semantics would be copying pre-existing binding (which is pretty
> optimized anyway).

>  The dict literal looks to me to be made up entirely of immutable
> keys and values, so the value of that literal expression seems to me
> to be a constant. If you had call time evaluation, you would be
> evaluating that expression each time, and the result would be a
> fresh mutable dict with that constant initial value each time. ISTM
> that could be optimized as
> param=private_dict_compile_time_created_from_literal.copy().  OTOH,
> if you used a pre-computed binding like DEFAULT_LIST, and wrote
> 
>       SHARED_DICT = {'1': 'A', '2': 'B', '3': 'C', '4': 'D'}
>       def func(param=SHARED_DICT):
>           pass
> 
> 
> then at def-time the compiler would not see the literal, but rather
> a name bound to a mutable dict instance. The call-time effect would
> be to bind param to whatever SHARED_DICT happened to be bound to,
> just like for DEFAULT_LIST. But the semantics, given analysis that
> showed no change to the SHARED_DICT _binding_ before the func call,
> would be to share a single mutable dict instance. This is unlike the
> semantics of
> 
>       def func(param={'1': 'A', '2': 'B', '3': 'C', '4': 'D'}):
>           pass
> 
> which implies a fresh mutable dict instance bound to param, with the
> same initial value (thus "constant" in a shallow sense at least,
> which in this case is fully constant).

Good analysis.


>>Well, personally, I don't see much use for non-constant default
>>arguments, as we have them now, wheras they would be useful if you
>>could get a fresh copy.  And, frankly, the default arguments feel like
>>they should be evaluated at call time.  Now that we have nested
>>scopes, there's no need for them to simulate closures.  So, from a
>>purely language perspective, I think they ought to be evaluated at
>>call time.
> 
> I'd worry a bit about the meaning of names used in initialization expressions
> if their values are to be looked up at call time. E.g., do you really want
> 
>   a = 2
>   def foo(x=a): print 'x =', x
>   ...
>   ...
>   a = 'eh?'
>   foo()
> 
> to print 'eh?' By the time you are past a lot of ...'s, ISTM the
> code intent is not so clear. But you can make dynamic access to the
> current a as a default explicit by
> 
>   class Defer(object):
>      def __init__(self, lam): self.lam = lam
> 
>   def foo(x=Defer(lambda:a)):
>       if isinstance(x, Defer): x=x.lam()
>       print 'x =', x
> 
> The semantics are different. I'd prefer to have the best of both
> worlds and be able to do both, as now, though I might not object to
> some nice syntactic sugar along the lines suggested by OP Stian
> S?iland. E.g., short spelling for the above Defer effect:
> 
>   def foo(x:a): print 'x =', x

All good points; doing something like this always seems to have
further repurcussions.


-- 
CARL BANKS                      http://www.aerojockey.com/software
"If you believe in yourself, drink your school, stay on drugs, and
don't do milk, you can get work." 
          -- Parody of Mr. T from a Robert Smigel Cartoon




More information about the Python-list mailing list