• A Single Instance of an Object?

    From Ivan \"Rambius\" Ivanov@3:633/280.2 to All on Tue Mar 12 07:53:00 2024
    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

    --- MBSE BBS v1.0.8.4 (Linux-x86_64)
    * Origin: ---:- FTN<->UseNet Gate -:--- (3:633/280.2@fidonet)
  • From Chris Angelico@3:633/280.2 to All on Tue Mar 12 07:57:51 2024
    On Tue, 12 Mar 2024 at 07:54, Ivan "Rambius" Ivanov via Python-list <python-list@python.org> wrote:
    I am refactoring some code and I would like to get rid of a global
    variable. Here is the outline:

    ...

    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

    --- MBSE BBS v1.0.8.4 (Linux-x86_64)
    * Origin: ---:- FTN<->UseNet Gate -:--- (3:633/280.2@fidonet)
  • From dn@3:633/280.2 to All on Tue Mar 12 08:03:01 2024
    Good question Rambius!

    On 12/03/24 09:53, Ivan "Rambius" Ivanov via Python-list wrote:
    Hello,

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

    import subprocess

    CACHE = {}

    First thought: don't reinvent-the-wheel, use lru_cache (https://docs.python.org/3/library/functools.html)


    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 = {}


    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

    --- MBSE BBS v1.0.8.4 (Linux-x86_64)
    * Origin: DWM (3:633/280.2@fidonet)
  • From Ivan \"Rambius\" Ivanov@3:633/280.2 to All on Tue Mar 12 08:04:32 2024
    On Mon, Mar 11, 2024 at 5:01=E2=80=AFPM Chris Angelico via Python-list <python-list@python.org> wrote:

    On Tue, 12 Mar 2024 at 07:54, Ivan "Rambius" Ivanov via Python-list <python-list@python.org> wrote:
    I am refactoring some code and I would like to get rid of a global variable. Here is the outline:

    ...

    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

    --=20
    Tangra Mega Rock: http://www.radiotangra.com

    --- MBSE BBS v1.0.8.4 (Linux-x86_64)
    * Origin: ---:- FTN<->UseNet Gate -:--- (3:633/280.2@fidonet)
  • From Ivan \"Rambius\" Ivanov@3:633/280.2 to All on Tue Mar 12 08:37:34 2024
    On Mon, Mar 11, 2024 at 5:06=E2=80=AFPM dn via Python-list <python-list@python.org> wrote:

    Good question Rambius!

    On 12/03/24 09:53, Ivan "Rambius" Ivanov via Python-list wrote:
    Hello,

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

    import subprocess

    CACHE =3D {}

    First thought: don't reinvent-the-wheel, use lru_cache (https://docs.python.org/3/library/functools.html)


    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 =3D {}


    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


    --=20
    Tangra Mega Rock: http://www.radiotangra.com

    --- MBSE BBS v1.0.8.4 (Linux-x86_64)
    * Origin: ---:- FTN<->UseNet Gate -:--- (3:633/280.2@fidonet)
  • From Chris Angelico@3:633/280.2 to All on Tue Mar 12 08:45:10 2024
    On Tue, 12 Mar 2024 at 08:04, Ivan "Rambius" Ivanov <rambiusparkisanius@gmail.com> wrote:
    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

    --- MBSE BBS v1.0.8.4 (Linux-x86_64)
    * Origin: ---:- FTN<->UseNet Gate -:--- (3:633/280.2@fidonet)
  • From Peter J. Holzer@3:633/280.2 to All on Tue Mar 12 10:37:14 2024

    --ps5sbj6en6ned4o5
    Content-Type: text/plain; charset=us-ascii
    Content-Disposition: inline
    Content-Transfer-Encoding: quoted-printable

    On 2024-03-11 16:53:00 -0400, Ivan "Rambius" Ivanov via Python-list wrote:
    I am refactoring some code and I would like to get rid of a global
    variable. Here is the outline:

    =2E..

    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:
    =20
    class Lookup:
    def __init__(self):
    self.cache =3D {}
    =20
    def lookup(key):
    if key in self.cache:
    return self.cache[key]
    =20
    value =3D None
    =20
    cmd =3D f"mycmd {key}"
    proc =3D subprocess(cmd, capture_output=3DTrue, text=3DTrue, chec=
    k=3DFalse)
    if proc.returncode =3D=3D 0:
    value =3D proc.stdout.strip()
    else:
    logger.error("cmd returned error")
    =20
    self.cache[key] =3D value
    return value
    =20
    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 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.
    =20
    How would you advise me to implement that?

    Just add a dict of Lookup objects to your module:

    lookups =3D {}

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

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

    import lookup

    lo =3D lookup.get_lookup("whatever")

    =2E..
    v =3D lo.lookup("a key")

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

    lo =3D lookup.get_lookup("test1")
    =2E..
    lo =3D lookup.get_lookup("test2")
    =2E..
    lo =3D 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.

    --=20
    _ | Peter J. Holzer | Story must make more sense than reality.
    |_|_) | |
    | | | hjp@hjp.at | -- Charles Stross, "Creative writing
    __/ | http://www.hjp.at/ | challenge!"

    --ps5sbj6en6ned4o5
    Content-Type: application/pgp-signature; name="signature.asc"

    -----BEGIN PGP SIGNATURE-----

    iQIzBAABCgAdFiEETtJbRjyPwVTYGJ5k8g5IURL+KF0FAmXvlaQACgkQ8g5IURL+ KF3XYBAAnXGsZsJfRJVxP+RX6kFJzhLVDYaHQV8dp8gpXX4XvKCCBo56Q2OyUqmk TDXhsn/GBpNIrPWmYmPAiL8pLSV3MbcON1ABGT9KgB+pxO8KnYVpH7Il6uV6zT8H ZvEXJwX8qjD5OtZIJ5q4pLaYW4A2l+8vvNS+pYqhFMOkRsteKld4XBVblw/+ME3/ 5Kat7h1/eoa4UVkHNgxB1FEWWKH6nXbXGgdM+khBBdO7SVI6vh9ooPLGZUvNGk8+ VDv3zc0O9fauEPzfPlBnSZQiyxCFyEgs0XF0hAMGQCadm5EZXHMu1dKi8+me0he3 laTb6DX9iiZbvbPVhITGmEh3UyaqAjYLfrhQ89Vwj2n1piKX611UNayWaUCVMVPy KfL1/lmaMc7Z1LJsOZG+kHTmSbGi6f9OTJpFZwJYOD5VP0SWVPyLGaJuO1azkP+c +tFYpm6UXPREFOsK8mSejWiQF5/klRCWLjjqS5rNfrsjp1I+lerUL/B3/bXTHc95 phoMSuRiuBqbmNzEOUm9dxSbVWkcWIOT6cgPZnpdGdJp7hdKShbTG854X4Zlli8l rVC1IfEn5ZIxLedVrMNccX2NDhEdyIAQuSYmtW2EJ7P2wnz/wsf3GFtyZpQrop9u +pLPOUH4HusZzOvro/AqZQcxCO+57+F9P4i4xcv+kCuVLzK86+Q=
    =o4EG
    -----END PGP SIGNATURE-----

    --ps5sbj6en6ned4o5--

    --- MBSE BBS v1.0.8.4 (Linux-x86_64)
    * Origin: ---:- FTN<->UseNet Gate -:--- (3:633/280.2@fidonet)