Python Forum
Prefs class: singleton or something else?
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Prefs class: singleton or something else?
#1
I've got a singleton class I'm using that works pretty well but I have several other classes (in different .py files) so I have to create it in every one (Hence the singletopn). Problem is that the prefs class loads from disk and since I have several modules using it I end up loading the same things many times.

Is there a way I can declare just once and use that in imported classes or some way for me to check if the new prefs class is the first instance so I can only load at that time? Or is there something else I can use that would work better?

I have about 1 month of python experience so I'm quite the beginner.

Thanks
Reply
#2
You do not load the same module multiple times. You load the module once. Additional imports reference the existing module.

I don't understand most of what you are saying. I have no idea what the "prefs" class is, or what you mean by "(Hence the singletopn)". If you are going to reference code, you should post the code. It is difficult to determine if there is "something else" that will "work better" when you haven't presented anything.

If you find yourself making a lot of files, you are probably doing something wrong. Python organizes code into modules, classes and functions that work together to achieve a goal. A module usually has more than one class. It is common for one module (or file) to have contain many classes. If you have multiple classes that are intimately related, they belong in the same module.
Reply
#3
Here's the singleton preferences class below

This is in a .py file by itselfs. I can't load it in one module then use it in all of them because it doesn't exist in any of the other .py files. I could put all of my classes into one big file then use the singleton a single time for use in all classes but I have a hard time finding anything when it's all one big file (that's why includes where invented right?).

This singleton is indeed the same in every instance but I can't seem to figure out a why to load the data from disk just once... Well, I could have the main program call the loading instead of doing it inside the prefs class itself but that is all I can think of.

Edit: Code snippet tag doesn't seem to work on this forum (or I just can't figure it out)
#Singleton Preferences

from PySide6.QtGui import QColor,QFont
from PySide6.QtCore import Qt
from os import path
from pubsub import pub
import json

class Preferences(object):
	def __new__(self):
		""" creates a singleton object, if it is not created,
		or else returns the previous singleton object"""
		if not hasattr(self, 'instance'):
			self.instance = super(Preferences, self).__new__(self)
		return self.instance


	def __init__(self):
		self._dict = None
#<Todo>
# Singleton Preferences
# figure out how to tell if it already exists so only loads once
#</Todo>
		if path.exists("prefs.json"):
			self.loadPrefs()
		else:
			self.setDefaults()

	def getFont(self):
		return QFont(self._dict["fontFamily"],self._dict["fontSize"])
	
	def setFontFamily(self,val):
		print(val)
		self._dict["fontFamily"] = val
	
	def getFontSize(self):
		return self._dict["fontSize"]
		
	def setFontSize(self,val):
		self._dict["fontSize"] = val
		
	def getFontColor(self):
		return QColor.fromString(self._dict["fontColor"])
	
	def setFontColor(self,val):
		self._dict["fontColor"] = val

	def getButtonWidth(self):
		return self._dict["buttonWidth"]
 
	def getButtonHeight(self):
		return self._dict["buttonHeight"]

	def showDict(self):
		print(self._dict)
		
	def setDefaults(self):
		self._dict = {
		"fontFamily":"None",
		"fontSize":16,
		"fontColor": "white",
		"buttonWidth":120,
		"buttonHeight":40
		}
		self.savePrefs()

	def loadPrefs(self):
		print("Loading singleton Preferences")
		with open('prefs.json', 'r') as openfile:
			self._dict = json.load(openfile)

	def savePrefs(self):
		self.showDict()
		print("Save singleton Prefs")
		_prefs = json.dumps(self._dict, indent=4)
		with open("prefs.json", "w") as outfile:
			outfile.write(_prefs)
Gribouillis write Dec-12-2022, 09:21 AM:
Please post all code, output and errors (it it's entirety) between their respective tags. Refer to BBCode help topic on how to post. Use the "Preview Post" button to make sure the code is presented as you expect before hitting the "Post Reply/Thread" button.
Reply
#4
You could initialize the unique instance in the __new__ static method
import time

class A:
    _instance = None
    def __new__(cls, *args, **kwargs):
        if not A._instance:
            A._instance =  super().__new__(cls)
            A._instance.load()
        return A._instance

    def __init__(self, *args, **kwargs):
        print('__init__ is always called')

    def load(self):
        print('load() is called only once')
        self.value = time.perf_counter()

a = A()
b = A()
print(a.value == b.value)
Output:
load() is called only once __init__ is always called __init__ is always called True
Reply
#5
I couldn't find anything on static classes when I looked, thanks!

(Dec-12-2022, 09:50 AM)Gribouillis Wrote: You could initialize the unique instance in the __new__ static method
import time

class A:
    _instance = None
    def __new__(cls, *args, **kwargs):
        if not A._instance:
            A._instance =  super().__new__(cls)
            A._instance.load()
        return A._instance

    def __init__(self, *args, **kwargs):
        print('__init__ is always called')

    def load(self):
        print('load() is called only once')
        self.value = time.perf_counter()

a = A()
b = A()
print(a.value == b.value)
Output:
load() is called only once __init__ is always called __init__ is always called True
Reply
#6
Yes that worked perfectly, only loaded once despite being called five or six times! Like I said, I started with Python about a month ago so I'm quite ignorant about the language so this was a great help!

I sort of understand a bit more about classes now. At first I thought "self" was a special word but now I understand it is just a regular variable name like myvar or someval. I could all it "extrovert" and it wouldn't make any difference. It just holds a reference to that particular instance.

I just have some beautifucation to complete (and some error handling) then my first python project will be complete (well, beta stage anyway). I'll post it here for review (in the other forum of course).
Reply
#7
(Dec-12-2022, 07:31 PM)PatM Wrote: Like I said, I started with Python about a month ago so I'm quite ignorant about the language so this was a great help!
There are alternatives to using a singleton class. For example you could create a unique shared object with a cached property that returns always the same instance of the Preferences class. The advantage of this is that the Preferences class remains a normal class with a simple code, which is better for its future.

from functools import cached_property

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

    def load_prefs(self):
        print('load_prefs() was called')
        self.spam = 'spam'

    ...


class _Shared:

    @cached_property
    def preferences(self):
        p = Preferences()
        p.load_prefs()
        return p

shared = _Shared()  # create unique instance of the shared object


if __name__ == '__main__':

    a = shared.preferences
    b = shared.preferences
    c = shared.preferences

    print(a)
    print(a is b, a is c)
Also you could add other global objects as attributes of the unique 'shared' object.

Output:
load_prefs() was called <__main__.Preferences object at 0x7f6e39a57dc0> True True
Reply
#8
Wow lots to learn! If it works the way I think then @cached_property looks like the closest thing to what I want to achieve. Although the last static singleton is pretty damned close and works perfect.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  singleton using metaclass bb8 12 8,701 Feb-13-2018, 01:27 PM
Last Post: bb8

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020