Pre-PEP: Dynamically evaluating default function arguments

Jeff Epler jepler at unpythonic.net
Tue Jan 6 18:33:31 EST 2004


That proposal gets "-All" from me. (or, at least, I think that's what I
mean.  Maybe I just mean "None", I couldn't stand that other thread)

Reams of code depend on default arguments working as they do today.  For
instance:
    l = []
    for i in range(10):
        l.append(lambda x, y=i: x+y)
or
    def fib(x, y={0: 1, 1: 1}):
        assert x >= 0
        if not y.has_key(x):
            y[x] = fib(x-1) + fib(x-2)
        return y[x]
or
    def stack():
	def push(x, l=[]):
		l.append(x)
	def pop(l=push.func_defaults[0]):
		return l.pop()
	return push, pop

It's not clear exactly how this feature would work.  What additional
state will you store with function objects to make it work?  Right now,
it works like this:
    def f(l=x):
        pass
is equivalent to
    _magic = x
    def f(*args):
        assert len(args) < 2
        if args: l = args[0]
        else: l = _magic
you're proposing something more like
    _magic = lambda: x
    def f(*args):
        assert len(args) < 2
        if args: l = args[0]
        else: l = _magic()
you've just added one additional function call overhead for each
defaulted argument, plus the time to evaluate the expression 'x', even
in cases where it makes no difference.  Here's a case where the
programmer has performed a micro-optimization that will probably be
slower after your change (even though the program's meaning is not
changed):
    def log10(x, l=math.log, l10=math.log(10)):
        return l(x)/l10

That leaves the case where you actually want the default argument value
to depend on the current program's state, as below:
    def log(s, when=time.time()):
        print >>stderr, time.asctime(when), s
I think it's better to write
    def log(s, when=None):
        if when is None: when = time.time()
        print >>stderr, time.asctime(when), s
because it makes calling code easier, too:
    def log_with_prefix(p, s, when=None):
        log("%s: %s" % (p, s), when)
instead of repeating the default argument everwhere you may call it both ways
    def log_with_prefix(p, s, when=time.time()):
        log("%s: %s" % (p, s), when)
or having to test at each place you call:
    def log_with_prefix(p, s, when=None):
        if when is None:
            log("%s: %s" % (p, s))
        else:
            log("%s: %s" % (p, s), when)

Jeff




More information about the Python-list mailing list