Early and late binding [was Re: what does 'a=b=c=[]' do]

Neil Cerutti neilc at norwich.edu
Fri Dec 23 12:03:11 EST 2011


On 2011-12-23, Steven D'Aprano <steve+comp.lang.python at pearwood.info> wrote:
> On Fri, 23 Dec 2011 13:13:38 +0000, Neil Cerutti wrote:
>> On 2011-12-23, Neil Cerutti <neilc at norwich.edu> wrote:
>>> Is the misfeature that Python doesn't evaluate the default
>>> argument expression every time you call the function? What
>>> would be the harm if it did?
>> 
>> ...you know, assuming it wouldn't break existing code. ;)
>
> It will. Python's default argument strategy has been in use for
> 20 years. Some code will rely on it. I know mine does.

I'm aware of that. I should have put the question differently,
but you did guess what I meant to ask. Thanks for the dicussion.

> Early binding is simple to implement and simple to explain:
> when you define a function, the default value is evaluated
> once, and the result stored to be used whenever it is needed.
> The disadvantage is that it can lead to unexpected results for
> mutable arguments.
>
> Late binding is also simple to explain, but a little harder to
> implement. The function needs to store the default value as a
> piece of code (an expression) which can be re-evaluated as
> often as needed, not an object.
>
> The disadvantage of late binding is that since the expression
> is live, it needs to be calculated each time, even if it turns
> out to be the same result. But there's no guarantee that it
> will return the same result each time: 

That's its main *advantage*.

> consider a default value like x=time.time(), which will return
> a different value each time it is called; or one like x=a+b,
> which will vary if either a or b are changed. Or will fail
> altogether if either a or b are deleted. This will surprise
> some people some of the time and lead to demands that Python
> "fix" the "obviously buggy" default argument gotcha.

It's hard to see anyone being confused by the resultant
exception. It's much harder to figure out what's going wrong with
an early-bound mutable.

> If a language only offers one, I maintain it should offer early
> binding (the status quo). Why? Because it is more elegant to
> fake late binding in an early binding language than vice versa.
>
> To fake late binding in a language with early binding, use a
> sentinel value and put the default value inside the body of the
> function:
>
>     def func(x, y=None):
>         if y is None:
>             y = []
>         ...
>
> All the important parts of the function are in one place,
> namely inside the function.
>
> To fake early binding when the language provides late binding,
> you still use a sentinel value, but the initialization code
> creating the default value is outside the body of the function,
> usually in a global variable:
>
>     _DEFAULT_Y = []  # Private constant, don't touch.
>
>     def func(x, y=None):
>         if y is None:
>             y = _DEFAULT_Y
>         ...
>
> This separates parts of the code that should be together, and
> relies on a global, with all the disadvantages that implies.

I'd use a function attribute.

def func(x, y=None):
  if y is None:
    y = func.default_y
  ...
func.default_y = []

That's awkward only if you believe function attributes are
awkward. Even if common practice were instead to use a global
variable, as you did, it wouldn't be considered bad if it were a
common idiom. In case, a global that's not meant to be rebound or
mutated is the best possible variety.

However, I also wouldn't base the decision of late versus early
binding on how confusing it would be if you assume wrongly which
one you are getting. Most default function arguments are
literals, so in Python the question just doesn't come up until
you get mutabled.

The greater efficiency was probably what decided this question
for Python, right? Since late-binding is so easy to fake, is
hardly ever what you want, and would make all code slower, why do
it?

-- 
Neil Cerutti



More information about the Python-list mailing list