1

I've implemented a timer using QTimer inside a Singleton. The Singleton is implemented using the Borg pattern. If I start a QTimer with single shot inside a function of the Singleton it won't be executed. The same call in a function outside the Singleton works well.

This is the code:

#!/usr/bin/env python
import sys
from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import QApplication


class Borg():
    _shared_state = {}
    def __init__(self):
        self.__dict__ = self._shared_state


class Timers(Borg):
    def __init__(self):
        Borg.__init__(self)

    def update_not_working(self):
        QTimer().singleShot(2000, Timers().update_not_working)
        print('update not working')


def update_working():
    QTimer().singleShot(2000, update_working)
    print('update working')

if __name__ == '__main__':
    app = QApplication(sys.argv)
    print('start timer')
    Timers().update_not_working()
    update_working()

    sys.exit(app.exec_())

The output is (no error, no exception):

start timer
update not working
update working
update working
....

Why is one call working and the other not? Is there something wrong with my implementation of the Borg or with the usage of QTimer?

ipa
  • 1,496
  • 1
  • 17
  • 36

3 Answers3

1

print self in update_not_working and print Timers() in update working show that the Timers object before the event loop started is different from the one within:

update not working
<__main__.Timers instance at 0xb52162cc>
update working
<__main__.Timers instance at 0xb52162cc>
update working
<__main__.Timers instance at 0xb521650c>
update working
<__main__.Timers instance at 0xb521650c>
update working
<__main__.Timers instance at 0xb521650c>
update working
<__main__.Timers instance at 0xb521650c>

@classmethod should help here because it allows to call the method on an instance OR on the class as you do in the single shot statement.

Compare: When should I use @classmethod and when def method(self)?

Community
  • 1
  • 1
Aaron
  • 1,181
  • 7
  • 15
  • The @classmethod solved the problem. What I'm still confused is, why there is no error occurring. If the object is different and can not be called by QTimer().singleShot(), shouldn't there be an exception? – ipa Apr 13 '16 at 15:37
  • No, if an qobject disppears it is usually properly destroyed and just disconned. That happens quietly because object are usually destroyed intentionally. – Aaron Apr 13 '16 at 15:49
1

This is actually just a matter of normal garbage-collection.

If you add some debugging code to your example like this:

class Timers(Borg):
    def __init__(self):
        Borg.__init__(self)
        print('init:', self)

    def update_not_working(self):
        QTimer().singleShot(1, Timers().update_not_working)
        print('update not working')

    def __del__(self):
        print('deleted:', self)

it will produce output like this:

start timer
init: <__main__.Timers object at 0x7f194bf53eb8>
init: <__main__.Timers object at 0x7f1940cfdb00>
deleted: <__main__.Timers object at 0x7f1940cfdb00>
update not working
deleted: <__main__.Timers object at 0x7f194bf53eb8>
update working
update working

As you can see, both of the Timers instances get deleted long before the single-shot timer sends its timeout() signal. And when they deleted, their instance-methods get deleted as well, which will automatically disconnect them from the signal. This shows that the Borg pattern does not produce a true singleton: it just mimics some of the behaviour of one.

If you use a real singleton class, like this:

class Timers2(object):
    _instance = None

    def __new__(cls):
        if Timers2._instance is None:
            Timers2._instance = object.__new__(cls)
        return Timers2._instance

    def update_not_working(self):
        QTimer().singleShot(2000, Timers2().update_not_working)
        print('update not working')

your example will work as expected. This is because there is only ever one instance, and it is kept alive by being cached as a class attribute.

Finally, the reason why the update_working() succeeds, is because it is a globally defined function. As with the class attribute, this ensures that it won't get garbage-collected until the script completes.

ekhumoro
  • 115,249
  • 20
  • 229
  • 336
0
class Borg():
    _shared_state = {}
    def __init__(self):
        self.__dict__ = self._shared_state


class Timers(Borg):
    def __init__(self):
        Borg.__init__(self)

    @classmethod
    def update_not_working(cls):
        QTimer().singleShot(2000, Timers().update_not_working)
        print('update not working')


def update_working():
    QTimer().singleShot(2000, update_working)
    print('update working')

if __name__ == '__main__':
    app = QApplication(sys.argv)
    print('start timer')
    Timers().update_not_working()
    update_working()
    sys.exit(app.exec_())
Taylan
  • 736
  • 1
  • 5
  • 14