class Node:
    def __init__(self, edges = set()):
        self.edges = edges


def main():
    foo = Node()
    bar = Node()
    quz = Node()

    foo.edges.add(bar)
    bar.edges.add(foo)

    assert(foo is not bar) # assertion succeeds
    assert(foo is not quz) # assertion succeeds
    assert(bar is not quz) # assertion succeeds
    assert(len(quz.edges) == 0) # assertion fails??


main()
spoiler

Mutable default values are shared across objects. The set in this case.

  • FizzyOrange
    link
    fedilink
    arrow-up
    1
    ·
    1 hour ago

    Yeah Pylint catches this. If you aren’t using Pylint you are writing bad Python.

  • Kevin
    link
    fedilink
    arrow-up
    7
    ·
    edit-2
    4 hours ago

    Oh I had a similar bug but with defaulted dicts. Default args are constructed once and reused. Not a problem for immutable args, but mutables like dicts (and sets I’d also assume) are all shared.

    EDIT: whoops, didn’t see you spoilered the answer, my bad! If it helps, i found my bug when dealing with cross-thread stuff, so that was a fun moment to bisect

    • unalivejoy@lemm.ee
      link
      fedilink
      English
      arrow-up
      3
      ·
      4 hours ago

      You may like collections.defaultdict. Pass the constructor a factory function to be run when a key is missing.

      dd = defaultdict(list)
      dd['key'].append("value")
      print(dd['key'])  # ["value"]
      
      • Kevin
        link
        fedilink
        arrow-up
        1
        ·
        3 hours ago

        Ah sorry I meant a default argument which was a dict, thanks for the tip tho!

  • m_f@discuss.online
    link
    fedilink
    English
    arrow-up
    1
    ·
    edit-2
    3 hours ago

    Yeah, I discovered this when a coworker wrote code like def foo(timestamp = now()) and had fun debugging why there were a bunch of duplicate timestamps.

    PEP 671 would add new syntax to ease the pain, but it’s not accepted yet. It would allow for writing function definitions like one of these:

    def bisect_right(a, x, lo=0, hi=>len(a), *, key=None):
    def connect(timeout=>default_timeout):
    def add_item(item, target=>[]):
    def format_time(fmt, time_t=>time.time()):