3

There's singleton class in python:

from PyQt5.QtCore import QObject, pyqtSignal
import logging

class Singleton(QObject):
    _instance = None
    def __new__(cls, *args, **kwargs):
        if not isinstance(cls._instance, cls):
            cls._instance = QObject.__new__(cls, *args, **kwargs)
        return cls._instance


class DataStatus(Singleton, QObject):
    '''
    '''
    dataChanged = pyqtSignal(str)
    __val = 'init'

    def __init__(self):
        super().__init__()

    def setVal(self, val):
        self.dataChanged.emit('emit: ' + val)
        logging.debug('emit: ' + val)
        self.__val = val

    def getVal(self):
        return self.__val

The idea is to have one single data store accessible from allover the program. Every time a set Method is called, a signal should be emitted telling all instances that from somewhere the data was changed and should be re-read.

Cool plan, but if you look at the test code

def test(self):     
    self.ds1 = DataStatus()
    self.ds1.dataChanged.connect(self.windowaction)
    print(self.ds1)
    print(self.ds1.getVal())

    self.ds1.setVal('ds1.first')

    self.ds2 = DataStatus()
    #self.ds2.dataChanged.connect(self.windowaction)
    print(self.ds2)
    print(self.ds2.getVal())

    self.ds2.setVal('ds2.second')

    print(self.ds1.getVal())

def windowaction(self, q):
    print(q)

And the console output it get's strange (at least for me):

<DataStatus.DataStatus.DataStatus object at 0x03207580>
init
emit: ds1.first
<DataStatus.DataStatus.DataStatus object at 0x03207580>
ds1.first
ds2.second

Both instances do have the same address, cool the singleton does it's job. To ds1 if've connected the "dataChange" signal which works properly if from ds1 data is updated. BUT no signal is received by ds1 if I change the data with ds2.set......

Does anybody have an explanation about what happens here. Data is shared properly across the instances, but not the signals:-/

Stefan
  • 113
  • 7

1 Answers1

4

Although your Singleton class complies that the same object is always returned but that does not imply that it is correctly implemented, in your case in new the new object is created but you return the first object created (fulfilling what you apparently want) but the signal "dataChanged "belongs to the new object and not to the first object causing the problem. The solution in this case is to use metaclasses as this library points out:

class Singleton(type(QObject), type):
    def __init__(cls, name, bases, dict):
        super().__init__(name, bases, dict)
        cls._instance = None

    def __call__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__call__(*args, **kwargs)
        return cls._instance


class DataStatus(QObject, metaclass=Singleton):
    dataChanged = pyqtSignal(str)
    __val = "init"

    def __init__(self):
        super().__init__()

    def setVal(self, val):
        self.dataChanged.emit("emit: " + val)
        logging.debug("emit: " + val)
        self.__val = val

    def getVal(self):
        return self.__val
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • First of all thank you! The code checker of PyDev does complain about the following lines: `def __init__(cls, name, bases, dict):` `def __call__(cls, *args, **kwargs):` It says: _Method '__init__ - DataStatus.DataStatus' should have self as first parameter_ If I replace _cls_ with _self_ all is still working. Therefore the question: is _cls_ a "style" element of metaclass, or is there more behind this? – Stefan Dec 26 '19 at 01:43
  • @Stefan It is only stylistic since a metaclass creates classes then "cls" is used, it is the same thing with "self" that is a custom that many linters adopt but if you replace it with another name like "this" it should continue to work. – eyllanesc Dec 26 '19 at 01:47