{
  "$type": "site.standard.document",
  "canonicalUrl": "https://rednafi.com/python/early-bound-function-defaults/",
  "description": "Avoid Python function default argument pitfalls caused by early binding, where mutable defaults and function calls bind at definition time.",
  "path": "/python/early-bound-function-defaults/",
  "publishedAt": "2022-01-27T00:00:00.000Z",
  "site": "at://did:plc:fgtm2c26vfcj74rfmeggbyqj/site.standard.publication/3mnl6f7ob462z",
  "tags": [
    "Python"
  ],
  "textContent": "I was reading a tweet about it yesterday and that didn't stop me from pushing a code change\nin production with the same rookie mistake today. Consider this function:\n\nHere, the function log has a parameter timestamp that computes its default value using\nthe built-in datetime.utcnow().isoformat() method. I was under the impression that the\ntimestamp parameter would be computed each time when the log function was called.\nHowever, that's not what happens when you try to run it. If you run the above snippet,\nyou'll get this instead:\n\nIn the __main__ block, I'm calling the log function 3 times with a 1-second delay\nbetween each invocation. But if you take a look at the timestamp of each of the log entries\nin the output, you'll notice that all 3 of them are exactly the same.\n\nDefault function arguments are early-bound in Python. That means:\n\n> Python interpreter will bind the default parameters at function definition time and will\n> use that static value at run time. It's also true for methods. This design choice was\n> intentional.\n\nWe're getting the same value of the timestamp each time because Python is computing the\nvalue of the default timestamp parameter once in the function definition time and then\nreusing the same value across all the function calls. The log function was called 3 times\nbut the timestamp function was invoked only once; during the function definition time.\n\nThis is easy to fix. Remove the default value of the timestamp and explicitly pass the\nparameter value while calling the function:\n\nNow if you run it, you'll get this:\n\nNotice, how the values of the seconds in the timestamps have roughly a 1-second delay\nbetween them. Early-bound defaults can also produce surprising results if you try to use a\nmutable data structure as the default value of a function/method. Here's an example:\n\nThe function append_to takes any object and appends that to the target mutable sequence.\nHere, the parameter target has a default value; an empty list. However, running the\nfunction reveals something unexpected:\n\nWhereas, you might expect it to print out the following:\n\nPython is reusing the same MutableSequence that was defined in the function definition\ntime; just like it was reusing the same return value of the datetime.utcnow().isoformat()\nin the previous section. To fix this you can do the following:\n\nRunning the snippet will produce the expected result this time:\n\nHere, I just omitted the target parameter from the append_to function signature.\nDefining the variable inside the function body can save you from being surprised at the most\nunfortunate time.\n\nBreadcrumbs\n\nCurrently, there's an outstanding PEP ([PEP-671]) that proposes late-bound function argument\ndefaults. It's still in a draft state and I'm quite fond of the syntax that it's proposing.\nHere's how you'd make a default parameter late-bound:\n\nThe default parameter baz will be late-bound and will produce similar results that we've\nseen in the last solution.\n\nFurther reading\n\n- [Mutable default arguments - The hitchhiker's guide to Python!]\n\n\n\n\n[pep-671]:\n    https://www.python.org/dev/peps/pep-0671\n\n[mutable default arguments - the hitchhiker's guide to python!]:\n    https://docs.python-guide.org/writing/gotchas/#mutable-default-arguments",
  "title": "Gotchas of early-bound function argument defaults in Python"
}