[{"data":1,"prerenderedAt":71},["ShallowReactive",2],{"qa-\u002Fpython\u002Fidioms\u002Fgotchas":3},{"page":4,"siblings":62,"blog":53},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":20,"path":21,"questions":22,"related":53,"seo":54,"seoDescription":55,"stem":56,"subtopic":57,"topic":58,"topicSlug":59,"updated":60,"__hash__":61},"qa\u002Fpython\u002Fidioms\u002Fgotchas.md","Gotchas",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"medium","md","Python","python",{},true,3,"\u002Fpython\u002Fidioms\u002Fgotchas",[23,28,32,36,40,44,48],{"id":24,"difficulty":25,"q":26,"a":27},"mutable-default-arg","hard","Why is a mutable default argument a gotcha?","A default argument is **evaluated once when the function is defined**, not on each\ncall. So a mutable default like `[]` or `{}` is **created a single time and shared**\nacross every call that doesn't override it — state leaks between calls.\n\n```python\ndef append_to(item, target=[]):   # the SAME list every call\n    target.append(item)\n    return target\n\nappend_to(1)   # [1]\nappend_to(2)   # [1, 2]  \u003C- surprise!\n\ndef append_to(item, target=None): # the fix: None sentinel\n    if target is None:\n        target = []               # fresh list per call\n    target.append(item)\n    return target\n```\n\nUse **`None` as the default** and create the real object inside the body. This is the\nsingle most famous Python footgun.\n",{"id":29,"difficulty":25,"q":30,"a":31},"late-binding-closures","What is the late-binding closure gotcha in loops?","Closures in Python capture **variables by reference, not by value**. When you create\nfunctions in a loop, they all close over the **same** loop variable, which holds its\n**final value** by the time they're called.\n\n```python\nfuncs = [lambda: i for i in range(3)]\n[f() for f in funcs]          # [2, 2, 2]  — not [0, 1, 2]!\n\n# fix: bind the current value via a default argument\nfuncs = [lambda i=i: i for i in range(3)]\n[f() for f in funcs]          # [0, 1, 2]\n```\n\nThe default-argument trick captures `i`'s value **at definition time**. (`functools.\npartial` works too.) Remember: closures see the variable's *latest* value, not a\nsnapshot.\n",{"id":33,"difficulty":14,"q":34,"a":35},"modify-while-iterating","What goes wrong when you modify a list while iterating it?","Changing a list's **size during iteration** shifts the internal index, causing\nelements to be **skipped or repeated**. With dicts and sets it's worse — Python\nraises `RuntimeError: dictionary changed size during iteration`.\n\n```python\nnums = [1, 2, 3, 4]\nfor n in nums:\n    if n % 2 == 0:\n        nums.remove(n)        # skips elements — buggy\nprint(nums)                   # [1, 3] for some inputs, wrong for others\n\nnums = [n for n in nums if n % 2]   # build a new list instead\n```\n\nFix it by iterating over a **copy** (`for n in nums[:]`) or, better, building a new\ncollection with a comprehension or `filter`. Never mutate a container's size while\nlooping over it.\n",{"id":37,"difficulty":14,"q":38,"a":39},"is-vs-eq-cached","Why does `is` give surprising results on numbers and strings?","`is` tests **identity** (same object), while `==` tests **value**. CPython **caches**\nsmall integers (−5 to 256) and **interns** some strings, so `is` *coincidentally*\nreturns `True` for those — but fails for values outside the cache, making it look\nunreliable.\n\n```python\na = 256; b = 256\na is b          # True  — cached\n\nc = 1000; d = 1000\nc is d          # often False — not cached\nc == d          # True   — always correct\n\nx = \"hi\"; y = \"hi\"\nx is y          # True (interned) — don't rely on it\n```\n\nCaching is an **implementation detail**, not a guarantee. Rule: use `==` for value\ncomparison; reserve `is` for singletons (`None`, `True`, `False`).\n",{"id":41,"difficulty":14,"q":42,"a":43},"bare-except","Why is a bare except an anti-pattern?","A bare **`except:`** (or `except Exception:` used carelessly) catches **everything** —\nincluding `KeyboardInterrupt` and `SystemExit` — and **swallows the error silently**,\nhiding bugs and making programs impossible to interrupt or debug.\n\n```python\ntry:\n    risky()\nexcept:                  # catches EVERYTHING, even Ctrl-C\n    pass                 # error vanishes — undebuggable\n\ntry:\n    risky()\nexcept ValueError as e:  # catch only what you expect\n    log.error(\"bad value: %s\", e)\n    raise                # or handle it deliberately\n```\n\nCatch the **specific exceptions** you can actually handle, and avoid `pass` in an\n`except` (at minimum log it). If you must catch broadly, use `except Exception` (not\nbare) so system-exiting signals still propagate.\n",{"id":45,"difficulty":25,"q":46,"a":47},"mutable-class-attr","Why is a mutable class attribute a common bug?","A mutable value assigned **at class level** (outside `__init__`) is **one object\nshared by every instance**. Mutating it through any instance affects **all** of them\n— usually not what you intend.\n\n```python\nclass Cart:\n    items = []                  # SHARED across all instances\n    def add(self, x):\n        self.items.append(x)\n\na, b = Cart(), Cart()\na.add(\"apple\")\nb.items                         # ['apple'] — leaked into b!\n\nclass Cart:\n    def __init__(self):\n        self.items = []         # per-instance — correct\n```\n\nInitialize mutable attributes **inside `__init__`** so each instance gets its own.\nClass-level attributes are fine for **immutable** constants\u002Fdefaults, but never for\nmutable per-instance state.\n",{"id":49,"difficulty":50,"q":51,"a":52},"shadowing-builtins","easy","What is the problem with shadowing builtins?","Naming a variable after a builtin — `list`, `dict`, `id`, `str`, `type`, `sum` —\n**shadows** it in that scope, so the original becomes unusable and you get confusing\nerrors later when you try to call it.\n\n```python\nlist = [1, 2, 3]          # shadows the built-in list type\nother = list((4, 5))      # TypeError: 'list' object is not callable\n\nid = 42                   # now id() is gone\nid(other)                 # TypeError: 'int' object is not callable\n```\n\nPick non-conflicting names: `items`\u002F`values` instead of `list`, `mapping` instead of\n`dict`, `user_id` instead of `id`. Linters flag builtin shadowing — heed the warning\nto avoid these baffling bugs.\n",null,{"description":11},"Python interview questions on common gotchas: mutable default arguments, late-binding closures, modifying a list while iterating, is vs ==, bare except, mutable class attributes, and shadowing builtins.","python\u002Fidioms\u002Fgotchas","Common Gotchas & Anti-patterns","Pythonic Idioms","idioms","2026-06-18","dQ6TWsvM0wnn7CS37n3Xav6bFViKq0g1Er9l_hofq4c",[63,67,70],{"subtopic":64,"path":65,"order":66},"EAFP vs LBYL","\u002Fpython\u002Fidioms\u002Feafp-lbyl",1,{"subtopic":68,"path":69,"order":12},"PEP 8 & Style","\u002Fpython\u002Fidioms\u002Fpep8-style",{"subtopic":57,"path":21,"order":20},1781808682427]