Discussion:
A Single Instance of an Object?
(too old to reply)
Ivan "Rambius" Ivanov
2024-03-11 20:53:00 UTC
Permalink
Hello,

I am refactoring some code and I would like to get rid of a global
variable. Here is the outline:

import subprocess

CACHE = {}

def lookup(key):
""""Runs the command cmd, parses its output, extract's the key's value,
caches it and returns it. If the key has already been in the cache,
returns its cached value. If the command cmd returns an error, the
value is set to None and cached as None."""

if key in CACHE:
return CACHE[key]

value = None

cmd = f"mycmd {key}"
proc = subprocess(cmd, capture_output=True, text=True, check=False)
if proc.returncode == 0:
value = proc.stdout.strip()
else:
logger.error("cmd returned error")

CACHE[key] = value
return value
...
def main():
while True:
keys = load_keys()
for key in keys:
call_function_that_call_function_that_calls_lookup(key)


The global cache variable made unit testing of the lookup(key) method
clumsy, because I have to clean it after each unit test. I refactored
it as:

class Lookup:
def __init__(self):
self.cache = {}

def lookup(key):
if key in self.cache:
return self.cache[key]

value = None

cmd = f"mycmd {key}"
proc = subprocess(cmd, capture_output=True, text=True, check=False)
if proc.returncode == 0:
value = proc.stdout.strip()
else:
logger.error("cmd returned error")

self.cache[key] = value
return value

Now it is easier to unit test, and the cache is not global. However, I
cannot instantiate Lookup inside the while- or for- loops in main(),
because the cache should be only one. I need to ensure there is only
one instance of Lookup - this is why I made it a global variable, so
that it is accessible to all functions in that script and the one that
actually needs it is 4 levels down in the call stack.

I have never done that in Python because I deliberately avoided such
complicated situations up to now. I know about the Singleton pattern,
but I have never implemented it in Python and I don't know if it is
Pythonish.

I am looking for the same behaviour as logging.getLogger(name).
logging.getLogger("myname") will always return the same object no
matter where it is called as long as the name argument is the same.

How would you advise me to implement that?

Regards
rambius
--
Tangra Mega Rock: http://www.radiotangra.com
Chris Angelico
2024-03-11 20:57:51 UTC
Permalink
On Tue, 12 Mar 2024 at 07:54, Ivan "Rambius" Ivanov via Python-list
Post by Ivan "Rambius" Ivanov
I am refactoring some code and I would like to get rid of a global
...
I have never done that in Python because I deliberately avoided such
complicated situations up to now. I know about the Singleton pattern,
but I have never implemented it in Python and I don't know if it is
Pythonish.
A Singleton is just a global variable. Why do this? Did someone tell
you "global variables are bad, don't use them"?

ChrisA
dn
2024-03-11 21:03:01 UTC
Permalink
Good question Rambius!
Post by Ivan "Rambius" Ivanov
Hello,
I am refactoring some code and I would like to get rid of a global
import subprocess
CACHE = {}
First thought: don't reinvent-the-wheel, use lru_cache
(https://docs.python.org/3/library/functools.html)
Post by Ivan "Rambius" Ivanov
The global cache variable made unit testing of the lookup(key) method
clumsy, because I have to clean it after each unit test. I refactored
self.cache = {}
Change "cache" to be a class-attribute (it is currently an instance.

Then, code AFTER the definition of Lookup can refer to Lookup.cache,
regardless of instantiation, and code within Lookup can refer to
self.cache as-is...
--
Regards,
=dn
Ivan "Rambius" Ivanov
2024-03-11 21:04:32 UTC
Permalink
On Mon, Mar 11, 2024 at 5:01 PM Chris Angelico via Python-list
Post by Chris Angelico
On Tue, 12 Mar 2024 at 07:54, Ivan "Rambius" Ivanov via Python-list
Post by Ivan "Rambius" Ivanov
I am refactoring some code and I would like to get rid of a global
...
I have never done that in Python because I deliberately avoided such
complicated situations up to now. I know about the Singleton pattern,
but I have never implemented it in Python and I don't know if it is
Pythonish.
A Singleton is just a global variable. Why do this? Did someone tell
you "global variables are bad, don't use them"?
I have bad experience with global variables because it is hard to
track what and when modifies them. I don't consider them bad, but if I
can I avoid them.

Regards
rambius
--
Tangra Mega Rock: http://www.radiotangra.com
Ivan "Rambius" Ivanov
2024-03-11 21:37:34 UTC
Permalink
On Mon, Mar 11, 2024 at 5:06 PM dn via Python-list
Post by dn
Good question Rambius!
Post by Ivan "Rambius" Ivanov
Hello,
I am refactoring some code and I would like to get rid of a global
import subprocess
CACHE = {}
First thought: don't reinvent-the-wheel, use lru_cache
(https://docs.python.org/3/library/functools.html)
Post by Ivan "Rambius" Ivanov
The global cache variable made unit testing of the lookup(key) method
clumsy, because I have to clean it after each unit test. I refactored
self.cache = {}
Change "cache" to be a class-attribute (it is currently an instance.
Then, code AFTER the definition of Lookup can refer to Lookup.cache,
regardless of instantiation, and code within Lookup can refer to
self.cache as-is...
Thank you for your suggestions. I will research them!

Regards
rambius
--
Tangra Mega Rock: http://www.radiotangra.com
Chris Angelico
2024-03-11 21:45:10 UTC
Permalink
On Tue, 12 Mar 2024 at 08:04, Ivan "Rambius" Ivanov
Post by Ivan "Rambius" Ivanov
Post by Chris Angelico
A Singleton is just a global variable. Why do this? Did someone tell
you "global variables are bad, don't use them"?
I have bad experience with global variables because it is hard to
track what and when modifies them. I don't consider them bad, but if I
can I avoid them.
If you have a singleton, how will you track "what and when modifies"
it? How is it any different from a global?

ChrisA
Peter J. Holzer
2024-03-11 23:37:14 UTC
Permalink
Post by Ivan "Rambius" Ivanov
I am refactoring some code and I would like to get rid of a global
...
Post by Ivan "Rambius" Ivanov
The global cache variable made unit testing of the lookup(key) method
clumsy, because I have to clean it after each unit test. I refactored
self.cache = {}
return self.cache[key]
value = None
cmd = f"mycmd {key}"
proc = subprocess(cmd, capture_output=True, text=True, check=False)
value = proc.stdout.strip()
logger.error("cmd returned error")
self.cache[key] = value
return value
Now it is easier to unit test, and the cache is not global. However, I
cannot instantiate Lookup inside the while- or for- loops in main(),
because the cache should be only one. I need to ensure there is only
one instance of Lookup - this is why I made it a global variable, so
that it is accessible to all functions in that script and the one that
actually needs it is 4 levels down in the call stack.
[...]
Post by Ivan "Rambius" Ivanov
I am looking for the same behaviour as logging.getLogger(name).
logging.getLogger("myname") will always return the same object no
matter where it is called as long as the name argument is the same.
How would you advise me to implement that?
Just add a dict of Lookup objects to your module:

lookups = {}

def get_lookup(name):
if name not in lookups:
lookups[name] = Lookup()
return lookups[name]

Then (assuming your module is also called "lookup", in all other modules
do

import lookup

lo = lookup.get_lookup("whatever")

...
v = lo.lookup("a key")

In your test cases where you need many different lookup tables use

lo = lookup.get_lookup("test1")
...
lo = lookup.get_lookup("test2")
...
lo = lookup.get_lookup("test3")

hp

PS: You don't have to put that in a separate module but I think it's a
lot cleaner that way.
--
_ | Peter J. Holzer | Story must make more sense than reality.
|_|_) | |
| | | ***@hjp.at | -- Charles Stross, "Creative writing
__/ | http://www.hjp.at/ | challenge!"
Loading...