@@ -138,8 +138,26 @@ 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
160+
143161 @abc .abstractmethod
144162 def send (self , gen : Generator [ResultT , ValueT , object ]) -> ResultT :
145163 """Send or throw the contained value or exception into the given
@@ -174,12 +192,22 @@ class Value(Outcome[ValueT], Generic[ValueT]):
174192 """The contained value."""
175193
176194 def __repr__ (self ) -> str :
177- return f'Value({ self .value !r} )'
195+ try :
196+ return f'Value({ self .value !r} )'
197+ except AttributeError :
198+ return f'Value(<AlreadyDestroyed>)'
178199
179200 def unwrap (self ) -> ValueT :
180201 self ._set_unwrapped ()
181202 return self .value
182203
204+ def unwrap_and_destroy (self ):
205+ self ._set_unwrapped ()
206+ v = self .value
207+ object .__delattr__ (self , "value" )
208+ return v
209+
210+
183211 def send (self , gen : Generator [ResultT , ValueT , object ]) -> ResultT :
184212 self ._set_unwrapped ()
185213 return gen .send (self .value )
@@ -202,7 +230,10 @@ class Error(Outcome[NoReturn]):
202230 """The contained exception object."""
203231
204232 def __repr__ (self ) -> str :
205- return f'Error({ self .error !r} )'
233+ try :
234+ return f'Error({ self .error !r} )'
235+ except AttributeError :
236+ return f'Error(<AlreadyDestroyed>)'
206237
207238 def unwrap (self ) -> NoReturn :
208239 self ._set_unwrapped ()
@@ -226,6 +257,29 @@ def unwrap(self) -> NoReturn:
226257 # __traceback__ from indirectly referencing 'captured_error'.
227258 del captured_error , self
228259
260+ def unwrap_and_destroy (self ) -> NoReturn :
261+ self ._set_unwrapped ()
262+ # Tracebacks show the 'raise' line below out of context, so let's give
263+ # this variable a name that makes sense out of context.
264+ captured_error = self .error
265+ object .__delattr__ (self , "error" )
266+ try :
267+ raise captured_error
268+ finally :
269+ # We want to avoid creating a reference cycle here. Python does
270+ # collect cycles just fine, so it wouldn't be the end of the world
271+ # if we did create a cycle, but the cyclic garbage collector adds
272+ # latency to Python programs, and the more cycles you create, the
273+ # more often it runs, so it's nicer to avoid creating them in the
274+ # first place. For more details see:
275+ #
276+ # https://github.com/python-trio/trio/issues/1770
277+ #
278+ # In particuar, by deleting this local variables from the 'unwrap'
279+ # methods frame, we avoid the 'captured_error' object's
280+ # __traceback__ from indirectly referencing 'captured_error'.
281+ del captured_error , self
282+
229283 def send (self , gen : Generator [ResultT , NoReturn , object ]) -> ResultT :
230284 self ._set_unwrapped ()
231285 return gen .throw (self .error )
0 commit comments