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
_______________________________________________
Python-ideas mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at
https://mail.python.org/archives/list/[email protected]/message/JCYPLWWMJQQ64CNNMQMT4YKMP7ZIBROD/
Code of Conduct: http://python.org/psf/codeofconduct/