Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does self = None do?

I'm reading the source code of the incoming asyncio package. Note that at the end of the method, there is a self = None statement. What does it do?

def _run(self):     try:         self._callback(*self._args)     except Exception as exc:         msg = 'Exception in callback {}{!r}'.format(self._callback,                                                     self._args)         self._loop.call_exception_handler({             'message': msg,             'exception': exc,             'handle': self,         })     self = None  # Needed to break cycles when an exception occurs. 

I thought it would erase the instance, but the following test doesn't suggest so:

class K:     def haha(self):         self = None  a = K() a.haha() print(a) # a is still an instance 
like image 550
Derek Chiang Avatar asked Mar 07 '14 08:03

Derek Chiang


People also ask

What does self -> None mean?

def __init__(self, n) -> None: means that __init__ should always return NoneType and it can be quite helpful if you accidentally return something different from None especially if you use mypy or other similar things. But you can ignore it if you prefer the old way to do it. Follow this answer to receive notifications.

What does -> None do in Python?

The None keyword is used to define a null value, or no value at all. None is not the same as 0, False, or an empty string.

What does None mean?

It is a type annotation for the main function that simply states that this function returns None . Type annotations were introduced in Python 3.5 and are specified in PEP 484 . Annotations for the return value of a function use the symbol -> followed by a type.

What does not none mean in Python?

It's often used as the default value for optional parameters, as in: def sort(key=None): if key is not None: # do something with the argument else: # argument was omitted. If you only used if key: here, then an argument which evaluated to false would not be considered.


2 Answers

It simply clears the local reference to self, making sure that if an exception occurs the reference passed to self._loop.call_exception_handler() is the only remaining reference and no cycle has been created.

This is still needed here because the local namespace is referenced by the exception traceback; it will not be cleared up when the function exits as there is a reference to the locals alive still.

This is documented in the sys.exc_info() function documentation with a warning:

Warning: Assigning the traceback return value to a local variable in a function that is handling an exception will cause a circular reference. This will prevent anything referenced by a local variable in the same function or by the traceback from being garbage collected. Since most functions don’t need access to the traceback, the best solution is to use something like exctype, value = sys.exc_info()[:2] to extract only the exception type and value. If you do need the traceback, make sure to delete it after use (best done with a try ... finally statement) or to call exc_info() in a function that does not itself handle an exception.

Because tulip handlers form a fundamental framework class the code handles the traceback circular reference case by removing self from the local namespace instead, as it cannot guarantee that the _callback or call_exception_handler functions will clear up their references.

In CPython, objects are destroyed when their reference count drops to 0, but a cyclic reference (a series of objects referencing themselves in a cycle) will never see their reference count drop to 0. The garbage collector does try to break such cycles but it cannot always do this or not fast enough. Explicitly clearing references avoids creating cycles.

For example, if there is a __del__ method, the garbage collector will not break a cycle as it won't know in what order to break a cycle safely in that case.

Even if there is no __del__ method (which a framework class should never assume will not be the case) it's best to not rely on the garbage collector eventually clearing cycles.

like image 159
Martijn Pieters Avatar answered Oct 26 '22 15:10

Martijn Pieters


Note that this line is introduced in revision 496 by Guido.

At this revision, the function that corresponded to _run is run:

def run(self):     try:         self._callback(*self._args)     except Exception:         tulip_log.exception('Exception in callback %s %r',                             self._callback, self._args)     self = None  # Needed to break cycles when an exception occurs. 

tulip_log is just a normal logger: logging.getLogger("tulip").

Under the hood, Logger.exception stores the result of sys.exc_info() in LogRecord, but the record object doesn't persist after the exception call.

To verify that logging.exception doesn't cause reference cycle, I did the following experiment:

import time  import logging  class T:     def __del__(self):         print('T.__del__ called')      def test(self):         try:             1 / 0         except Exception:             logging.exception("Testing")   def run():     t = T()     t.test()     # t is supposed to be garbaged collected   run()  time.sleep(10) # to emulate a long running process 

This is the result:

$ python test.py  ERROR:root:Testing Traceback (most recent call last):   File "test.py", line 11, in test     1 / 0 ZeroDivisionError: integer division or modulo by zero T.__del__ called 

The object t is garbage collected as expected.

So, I don't think that self = None assignment is necessary here.

like image 22
satoru Avatar answered Oct 26 '22 16:10

satoru