Skip to content

Commit

Permalink
Explain use of global thread-local variable in repr.
Browse files Browse the repository at this point in the history
It's not completely obvious to a reader why we need a thread-local
"global" variable to track reference cycles in `__repr__` calls,
and the test does not currently contain one. This change adds a comment
explaining the need and also adds a non-attrs value in the reference
cycle of `test_infinite_recursion`.
  • Loading branch information
thetorpedodog committed Nov 2, 2021
1 parent 5a368d8 commit f812ec5
Show file tree
Hide file tree
Showing 2 changed files with 12 additions and 2 deletions.
8 changes: 8 additions & 0 deletions src/attr/_make.py
Original file line number Diff line number Diff line change
Expand Up @@ -1864,6 +1864,14 @@ def _add_eq(cls, attrs=None):
return cls


# Thread-local global to track attrs instances which are already being repr'd.
# This is needed because there is no other (thread-safe) way to pass info
# about the instances that are already being repr'd through the call stack
# in order to ensure we don't perform infinite recursion.
#
# For instance, if an instance contains a dict which contains that instance,
# we need to know that we're already repr'ing the outside instance from within
# the dict's repr() call.
_already_repring = threading.local()

if HAS_F_STRINGS:
Expand Down
6 changes: 4 additions & 2 deletions tests/test_dunders.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,8 +364,10 @@ class Cycle(object):
cycle = attr.ib(default=None)

cycle = Cycle()
cycle.cycle = cycle
assert "Cycle(value=7, cycle=...)" == repr(cycle)
# Ensure that the reference cycle passes through a non-attrs object.
# This demonstrates the need for a thread-local "global" ID tracker.
cycle.cycle = [cycle]
assert "Cycle(value=7, cycle=[...])" == repr(cycle)

def test_underscores(self):
"""
Expand Down

0 comments on commit f812ec5

Please sign in to comment.