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

Reply via email to