Search code examples
pythonpytestpython-import

Why does importing a Python class instantiate it?


A somewhat cut down example here from some PyTest unit tests that never exit. If I stick a break point on DataListener.__init__ I see it getting called from the from controller import Controller statement in test_controller.py which was a bit of a suprise to me; Why does importing a class from a module instantiate an instance of it?

data_listener.py

class DataListener:

    def __init__(self, port, host='localhost'):
        self._stop_monitor = Event()
        self._monitor = Thread(target=self.check_last_message)
        self._monitor.start()

    def __del__(self):
        self.stop()

    def stop(self):
        self._stop_monitor.set()
        self._monitor.join()

    def check_last_message(self):
        while not self._stop_monitor.is_set():
            # Code to check for a heartbeat here
            pass

controller.py

from data_listener import DataListener

class Controller:
    def __init__(
        self,
        subscriber=DataListener(port=DEFAULT_SUBSCRIBER_PORT),
        publisher=JsonPublisher(DEFAULT_PUBLISHER_PORT)):

        pass

test_controller.py

from controller import Controller

def test_controller_work_loop():
    # ARRANGE
    controller = Controller(subscriber=None, publisher=None)

    # Some actual tests here
    # This test passes and finishes but the test suite never exits

Solution

  • Default arguments are evaluated when the class is loaded.

    from data_listener import DataListener
    
    class Controller:
        def __init__(
            self,
            subscriber=DataListener(port=DEFAULT_SUBSCRIBER_PORT),  # These are run!
            publisher=JsonPublisher(DEFAULT_PUBLISHER_PORT)):
    
            pass
    

    You need to update your Controller class to initialize them lazily in your initializer

    from data_listener import DataListener
    
    class Controller:
        def __init__(self, subscriber=None, publisher=None):
            self.subscriber = subscriber or DataListener(port=DEFAULT_SUBSCRIBER_PORT)
            self.publisher = publisher or JsonPublisher(DEFAULT_PUBLISHER_PORT)