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.
Yeah Pylint catches this. If you aren’t using Pylint you are writing bad Python.
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
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"]
Ah sorry I meant a default argument which was a dict, thanks for the tip tho!
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()):