Search code examples
pythonfunctionfunction-parameter

Is it ok to use module constants as default function arguments in Python?


Basically something like this:

DEFAULT_TIMEOUT = 10
# or even: from my_settings import DEFAULT_TIMEOUT

def get_google(timeout=DEFAULT_TIMEOUT):
    return requests.get('google.com', timeout=timeout)

I would assume that as long as the constant really stays constant this should work just fine. Yet I sometimes see a pattern like this:

DEFAULT_TIMEOUT = 10

def get_google(timeout=None):
    if timeout is None:
        timeout = DEFAULT_TIMEOUT
    return requests.get('google.com', timeout=timeout)

Are these equivalent or should I prefer one over the other?


Solution

  • There's no problem with using the "constant" as a default value. As you say, as long as the "constant" is really constant, it won't matter. The only thing is that you do have to make sure the constant is defined before the function, but usually people put all their constants at the top of the file so that's not an issue.

    The second pattern you describe is common when the desired default is a mutable value, such as a list. You often see things like this:

    def foo(x=None):
        if x is None:
            x = []
    

    instead of def foo(x=[]). You can find many questions about this, but essentially it is because if you don't do it this way, the mutable default will persist across multiple calls to the function, which usually isn't desirable.

    However, using this pattern for a mutable module-level constant wouldn't fix that problem. If you have:

    SOME_CONSTANT = []
    
    def foo(x=None):
        if x is None:
            x = SOME_CONSTANT
    

    . . . then you're still reusing the same mutable value across multiple calls. (Of course, defining a mutable value as a "constant" is probably not a good idea anyway.) That's why I was asking in the comments if you've seen someone specifically doing this kind of thing with module constants.

    This None-then-if pattern would also be used if the module-level default were actually not a constant, but a value intended to be changed by other code. If you do def foo(x=DEFAULT_TIMEOUT), the default value of x is whatever DEFAULT_TIMEOUT was at the time you defined the function. But if you use the None-then-if pattern, the default will be whatever DEFAULT_TIMEOUT is at the time you call the function. Some libraries define module-level values that aren't meant to be constant, but are rather configuration values that may be changed in the course of execution. This allows users to do things like set DEFAULT_TIMEOUT = 20 to change the default timeout for all subsequent calls, rather than having to pass timeout=20 every time. In this case you would want the if check inside the function, to ensure that every call uses the "current" value of DEFAULT_TIMEOUT.