Should `None` defaults for optional arguments be discouraged?

Contrary to most reactions, I find it natural and desirable to accept None as an alternative to omit an optional parameter in a wide variety of APIs. Not because I like to read or write log(2.3, None) but because I like to be able to define a simple wrapper, e.g.

def my_log(x, base=None):
    # <extra stuff here>
    return math.log(x, base)

This allows both my_log(x) and my_log(x, base) to be called, and my_log() doesn’t have to hard-code knowledge about the default base. While it is possible to write a wrapper that accepts an optional extra argument and passes that on only when present, it is uglier and harder to read, e.g.

def my_log(x, *args):
    assert len(args) in (0, 1))
    # <extra stuff here>
    return math.log(x, *args)

This is slower too (even if you take out the assert) because handling *args takes a slower path in the interpreter. And why should you have to do it this way?

(EDIT: The version with *args also makes it more awkward to access base in the wrapper in case the “extra stuff” wants to intercept a certain base.)

21 Likes