Document how to create Parameterized Singleton
- Dominant language
- Python
- Stars
- 521
- Forks
- 86
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 35
Description
# 🚀 Feature
Document how to create a `Parameterized` Singleton.
## Motivation
I'm trying to create an `AppStateWatcher` here https://github.com/Lightning-AI/lightning/pull/13531. For efficiency reasons only one instance should be created. To make it easy to use for users in their Panel apps I would like it to be a *singleton* as described in https://www.geeksforgeeks.org/singleton-pattern-in-python-a-complete-guide/.
When if followed the guide above it *did not work* for me. I saw strange behaviour when I had two windows open. Only one of the windows showed the update when I clicked a button. But I could trigger the the update from a button in both windows. At first this was quite confusing.
But I found out that the guide only describes how the `__new__` method should be implemented. But it turned out that something similar should be done for the `__init__` method. As far as I can see lots of guides and tutorials on singletons only describe the implementation of the `__new__` method.
I believe the singleton could be generally valuable. Normally in Panel when I want to share an instance across sessions I cache it in `pn.state.cache`. But here I would like to make the `AppStateWatcher` useful for other frameworks in lightning than Panel - thus the singleton came up as the answer.
### Does not work
```python
import param
class AppStateWatcher(param.Parameterized):
state = param.Parameter()
def __new__(cls):
# This makes the AppStateWatcher a *singleton*.
# The AppStateWatcher is a singleton to minimize the number of requests etc..
if not hasattr(cls, "_instance"):
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self):
super().__init__()
app1=AppStateWatcher()
print("app1", app1)
@param.depends(app1.param.state, watch=True)
def _update_a(state):
print(state)
app1.state="x"
app2=AppStateWatcher()
@param.depends(app2.param.state, watch=True)
def _update_b(state):
print(state)
app1.state="y"
app2.state="z"
print("app1", app1)
print("app2", app2)
```
```bash
$ python 'script.py'
app1
x
y
z
app1
app2
```
## Works
```python
import param
class AppStateWatcher(param.Parameterized):
state = param.Parameter()
def __new__(cls):
# This makes the AppStateWatcher a *singleton*.
# The AppStateWatcher is a singleton to minimize the number of requests etc..
if not hasattr(cls, "_instance"):
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self):
if not hasattr(self, "_init"):
super().__init__()
self._init = True
app1=AppStateWatcher()
print("app1", app1)
@param.depends(app1.param.state, watch=True)
def _update_a(state):
print(state)
app1.state="x"
app2=AppStateWatcher()
@param.depends(app2.param.state, watch=True)
def _update_b(state):
print(state)
app1.state="y"
app2.state="z"
print("app1", app1)
print("app2", app2)
```
```bash
$ python 'script.py'
app1
x
y
y
z
z
app1
app2
```
## Explanation
My hypothesis is that a second run of `__init__` resets the `watchers` and thus should be avoided.
Contributor guide
Assessment
This issue has not been assessed yet.