bslatkin / effectivepython

Effective Python: Second Edition — Source Code and Errata for the Book
https://effectivepython.com
2.2k stars 710 forks source link

Item 24 Use None and Docstrings to Specify Dynamic Default Arguments #86

Closed srob1 closed 1 month ago

srob1 commented 4 years ago

This is not an errata but rather a suggestion for an addition to Item 24.

Sometimes you cannot use None as the default value for a keyword argument because None is a valid value that the caller may specify. In that case, one can do something like the following:

from datetime import datetime

sentinel = object()

def log(message, when=sentinel):
    """Log a message with or without a timestamp.
    Args:
        message: Message to print.
        when: datetime of when the message occurred.
            Defaults to the present time.
            if when is None, the message is logged without a timestamp.
    """
    if when is None:
        print(f'{message}')
    else:
        if when is sentinel:
            when = datetime.now()
        print(f'{when}: {message}')

>>> log('Hi there!')                               # log with current time
2020-06-11 16:41:28.401196: Hi there!
>>> log('Hi there!', datetime(2020, 1, 1, 0, 0))   # log with specified time
2020-01-01 00:00:00: Hi there!
>>> log('Hi there!', None)                         # log without a time
Hi there!
>>> 

This extension to item 24 example 2 allows the caller to specify None for when in order to suppress the timestamp, perhaps for a message that is not associated with any particular time.

This is kind of a silly example, I'm sure you can come up with a better use case where you might want to have an argument with a default that could be None, but I think it would be helpful to explain how to deal with that situation as part of this Item.

bslatkin commented 1 month ago

Thank you for the suggestion! Sorry for my long delay. Yes, a sentinel will work if None is somehow significant like this. But generally I'd avoid defining an API like that if possible.