@@ -138,6 +138,23 @@ def unwrap(self) -> ValueT:
138138 x = fn(*args)
139139 x = outcome.capture(fn, *args).unwrap()
140140
141+ Note: this leaves a reference to the contained value or exception
142+ alive which may result in values not being garbage collected or
143+ exceptions leaving a reference cycle. If this is an issue it's
144+ recommended to call the ``unwrap_and_destroy()`` method
145+
146+ """
147+
148+ @abc .abstractmethod
149+ def unwrap_and_destroy (self ) -> ValueT :
150+ """Return or raise the contained value or exception, remove the
151+ reference to the contained value or exception.
152+
153+ These two lines of code are equivalent::
154+
155+ x = fn(*args)
156+ x = outcome.capture(fn, *args).unwrap_and_destroy()
157+
141158 """
142159
143160 @abc .abstractmethod
@@ -174,12 +191,21 @@ class Value(Outcome[ValueT], Generic[ValueT]):
174191 """The contained value."""
175192
176193 def __repr__ (self ) -> str :
177- return f'Value({ self .value !r} )'
194+ try :
195+ return f'Value({ self .value !r} )'
196+ except AttributeError :
197+ return f'Value(<AlreadyDestroyed>)'
178198
179199 def unwrap (self ) -> ValueT :
180200 self ._set_unwrapped ()
181201 return self .value
182202
203+ def unwrap_and_destroy (self ):
204+ self ._set_unwrapped ()
205+ v = self .value
206+ object .__delattr__ (self , "value" )
207+ return v
208+
183209 def send (self , gen : Generator [ResultT , ValueT , object ]) -> ResultT :
184210 self ._set_unwrapped ()
185211 return gen .send (self .value )
@@ -202,7 +228,10 @@ class Error(Outcome[NoReturn]):
202228 """The contained exception object."""
203229
204230 def __repr__ (self ) -> str :
205- return f'Error({ self .error !r} )'
231+ try :
232+ return f'Error({ self .error !r} )'
233+ except AttributeError :
234+ return f'Error(<AlreadyDestroyed>)'
206235
207236 def unwrap (self ) -> NoReturn :
208237 self ._set_unwrapped ()
@@ -226,6 +255,29 @@ def unwrap(self) -> NoReturn:
226255 # __traceback__ from indirectly referencing 'captured_error'.
227256 del captured_error , self
228257
258+ def unwrap_and_destroy (self ) -> NoReturn :
259+ self ._set_unwrapped ()
260+ # Tracebacks show the 'raise' line below out of context, so let's give
261+ # this variable a name that makes sense out of context.
262+ captured_error = self .error
263+ object .__delattr__ (self , "error" )
264+ try :
265+ raise captured_error
266+ finally :
267+ # We want to avoid creating a reference cycle here. Python does
268+ # collect cycles just fine, so it wouldn't be the end of the world
269+ # if we did create a cycle, but the cyclic garbage collector adds
270+ # latency to Python programs, and the more cycles you create, the
271+ # more often it runs, so it's nicer to avoid creating them in the
272+ # first place. For more details see:
273+ #
274+ # https://github.com/python-trio/trio/issues/1770
275+ #
276+ # In particuar, by deleting this local variables from the 'unwrap'
277+ # methods frame, we avoid the 'captured_error' object's
278+ # __traceback__ from indirectly referencing 'captured_error'.
279+ del captured_error , self
280+
229281 def send (self , gen : Generator [ResultT , NoReturn , object ]) -> ResultT :
230282 self ._set_unwrapped ()
231283 return gen .throw (self .error )
0 commit comments