Inspired by a recent post about the woes of __init__
methods in Python,
I thought I’d share the untold story of the absolute craziest __init__
I’ve come across in a production codebase.
It all started when I tried to add a failing test to a Python service.
The test was indeed failing, but every now and then it would fail on something unexpected.
The Evidence of the Test
After some minimization, this was the test I had:
def test_foobar():
f = FooBarWidget()
with contextlib.closing(f):
assert False
which sometimes failed with this error:
self = <foobar.FooBarWidget object at 0x10512ed80>
def close(self):
> if self.should_exit is False:
E AttributeError: 'FooBarWidget' object has no attribute 'should_exit'
foo.py:28: AttributeError
And not (only) the expected AssertionError
.
Searching for self.should_exit =
yielded FooWidget.__init__
in foo.py
:
class AbstractWidget:
def __init__(self):
self.config = Path("config.json").read_text()
class FooWidget(AbstractWidget):
def __init__(self):
super().__init__()
self.ctx = zmq.Context.instance()
self.consumer: zmq.Socket = self.ctx.socket(zmq.PULL)
self.should_exit = False
def run(self):
while self.should_exit is False:
...
self.consumer.close()
def close(self):
if self.should_exit is False:
self.should_exit = True
Which didn’t make sense at all: assuming none of these lines fail, how can the should_exit
attribute sometimes not be to set?
The __init__
The impossible could not have happened,
therefore the impossible must be possible in spite of appearances.
~ Hercule Poirot, Murder on the Orient Express
The only other clue we have is that self
is a FooBarWidget
, not a FooWidget
.
So, to the class FooBarWidget(FooWidget)
definition I went, which is where I found it.
The craziest __init__
I’ve ever seen:
class FooBarWidget(FooWidget):
def __init__(self):
self.publisher: zmq.Socket = zmq.Context.instance().socket(zmq.PUSH)
self._init()
def _init(self):
def worker_thread_start():
FooWidget.__init__(self)
self.run()
worker_thread = Thread(target=worker_thread_start, daemon=True)
worker_thread.start()
Yep. You read that right: this class kicks off it’s parent’s __init__
to a new thread.
So if you close
a FooBarWidget
instance too quickly, you just might do it before FooWidget.__init__
has finished,
resulting in pain, suffering, and a deep questioning of one’s life choices.
Why, Though?
Since you might be wondering why someone would even do this, first you need to know that a zmq.Socket
cannot be moved between threads,
as explained in the legendary ZeroMQ Guide.
To avoid blocking the main thread, FooBarWidget
wants to let the run
method execute in a different thread.
However, it cannot do so once FooWidget.__init__
has been called (since the self.consumer
socket will be created on the main thread).
A solution, then: let both the __init__
and the run
execute in the new thread,
solving an annoying problem with a complete and utter disregard to sanity, common sense, and the feelings of other human beings.
You can view the full code here.
I had desired it with an ardour that far exceeded moderation; but now that I had finished, the beauty of the dream vanished, and breathless horror and disgust filled my heart.
~ Frankenstein
If you liked this, you might also like My most downvoted StackOverflow answer and Making Python 100x faster with less than 100 lines of Rust.