On Thu, May 28, 2020 at 10:37:58PM +1200, Greg Ewing wrote:
I think we need a real example to be able to talk about this meaningfully.
But I'm having trouble thinking of one. I can't remember ever writing a function with a default argument value that *has* to be mutable and *has* to have a new one created on each call *unless* the caller provided one.
That is too strict. The "mutable default" issue is only a subset of late binding use-cases. The value doesn't even need to be mutable, it just needs to be re-evaluated each time it is needed, not just once when the function is defined. This is by no means an exhaustive list, just a small sample of examples from the stdlib. (1) The asyncore.py module has quite a few functions that take `map=None` parameters, and then replace them with a global: if map is None: map = socket_map If the global is re-bound to a new object, then the functions should pick up the new socket_map, not keep using the old one. So this would be wrong: def poll(..., map=socket_map) (2) The cgi module: def parse(fp=None, ...): if fp is None: fp = sys.stdin Again, `fp=sys.stdin` as the parameter would be wrong; the default should be the stdin at the time the function is called, not when the function was defined. (3) code.py includes an absolute classic example of the typical mutable default problem: class InteractiveInterpreter: def __init__(self, locals=None): if locals is None: locals = {"__name__": "__console__", "__doc__": None} If locals is not passed, each instance must have its own fresh mutable namespace. (4) crypt.mksalt is another example of late-binding: def mksalt(method=None, *, rounds=None): if method is None: method = methods[0] where the global `methods` isn't even defined until after the function is created. (5) The "Example" class from doctest is another case of the mutable default issue. (That's not an example class, but a class that contains examples extracted out of the docstring. Naming is hard.) class Example: def __init__(self, ..., options=None): ... if options is None: options = {} DocTestFinder contains another one: # globs defaults to None if globs is None: if module is None: globs = {} else: globs = module.__dict__.copy() DocTestRunner is another late-binding example: # checker defaults to None self._checker = checker or OutputChecker() (6) The getpass module has: def unix_getpass(prompt='Password: ', stream=None): ... If stream is None, it tries the tty, and if that fails, stdin. The code handling the None stream is large and complex, and so might not be suitable for a late-binding default since it would be difficult to cram it all into a single expression. Other parts of the module include `stream=None` defaults as a sentinel to switch to sys.stderr. In my own code, I have examples of late-binding defaults: if vowels is None: vowels = VOWELS.get(lang, '') if rand is None: rand = random.Random() among others. -- Steven