Topic: [Risolto] Possibile creare singleton con proprietà "statiche"?  (Letto 173 volte)

0 Utenti e 1 Visitatore stanno visualizzando questo topic.

Offline nuzzopippo

  • python sapiens
  • *****
  • Post: 670
  • Punti reputazione: 0
    • Mostra profilo
I miei saluti.
Forse è una domanda "noiosa" quella che sto per porre, ma si tratta di concetti sui quali mi sembra di arrancare troppo.

Da qualche giorno mi sto "sbattendo" sull'idea di creare delle classi "singleton" con un insieme di proprietà che rimangano "statiche" tra le varie istanze che vengono fatte.
A parte la banale definizione di una classe ed alla sua istanza all'interno di un modulo (da richiamare per l'uso) sin ora mi è riuscito di combinare qualcosa di utile solo ricorrendo alle variabili di classe, tipo l'esempio sotto (che sembra funzionante)
from __future__ import annotations
from typing import Any
from functools import wraps

import datetime
import os

def singleton(o_cls):
    orig_new = o_cls.__new__
    instance = None

    @wraps(o_cls.__new__)
    def __new__(cls, *args, **kwargs):
        nonlocal instance
        if instance is None:
            instance = orig_new(cls, *args, **kwargs)
        return instance
    o_cls.__new__ = __new__
    return o_cls


@singleton
class LogsWriter:
    ''' Scrive dei messaggi nel corrente file di log '''
    '''_instance = None
    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super(LogsWriter, cls).__new__(cls)
        return cls._instance'''
    _logname = ''
       
    def __init__(self) -> None:
        self.log_dir = ''
   
    def set_log_dir(self, logs_dir: str) -> None:
        if not os.path.exists(logs_dir) or not os.path.isdir(logs_dir):
            raise ValueError('Directory non valida')
        self.log_dir = logs_dir
        self._define()

    def _define(self) -> None:
        date = datetime.datetime.now()
        year = date.year
        name = f'Projects_{year}.log'
        f_name = os.path.join(self.log_dir, name)
        self._logname = f_name
   
    def make_log(self, msg: str) -> None:
        date = datetime.datetime.now()
        m = date.strftime('%Y-%m-%d_%H:%M:%S') + '- ' + msg + '\n'
        with open(self._logname, 'a') as f:
            f.write(m)

loger = LogsWriter()

perdendosi, naturalmente, le eventuali variabili di istanza tra una invocazione e l'altra ... ora vorrei implementare qualcosa di più articolato (e magari fatto meglio) quale singleton ma a parte la banalità generalizzata degli esempi che trovo in rete (forse non faccio le ricerche "giuste") od anche perché border-line per me alcuni concetti, tipo le meta-classi, sto girando in tondo.

Qualcuno saprebbe darmi qualche dritta interessante da consultare?
« Ultima modifica: Dicembre 15, 2022, 16:01 da nuzzopippo »

Offline GlennHK

  • python sapiens sapiens
  • ******
  • Post: 1.720
  • Punti reputazione: 1
    • Mostra profilo
    • La Tana di GlennHK
Re:Possibile creare singleton con proprietà "statiche"?
« Risposta #1 il: Dicembre 14, 2022, 17:18 »
Occhio quando fai roba del genere, perché se non ricordo male l'istanza ti viene reinizializzata ogni volta.


>>> @singleton
    ...: class C:
    ...:     def __init__(self):
    ...:         self.n = 0
    ...:

>>> c = C()
>>> c.n = 5
>>> c.n
5
>>> c2 = C()
>>> c is c2
True
>>> c.n
0
>>> c2.n
0


Questo è dovuto alla specifica di __new__, ovvero: "se restituisce un'istanza, invoca __init__ su quell'istanza".

Un modo per gestire la cosa è "staccare" __init__ dalla classe, invocarlo solo sull'istanza appena creata e mai più.

Offline nuzzopippo

  • python sapiens
  • *****
  • Post: 670
  • Punti reputazione: 0
    • Mostra profilo
Re:Possibile creare singleton con proprietà "statiche"?
« Risposta #2 il: Dicembre 15, 2022, 10:08 »
Grazie della risposta @GlennHk, riguardo :
Occhio quando fai roba del genere, perché se non ricordo male l'istanza ti viene reinizializzata ogni volta.
Me ne ero accorto, proprio quel punto mi stava suonando come un tamburo, ci giravo attorno senza veder soluzione sino alla Tua imbeccata :
...Un modo per gestire la cosa è "staccare" __init__ dalla classe, invocarlo solo sull'istanza appena creata e mai più.
Che mi ha dato una ispirazione che sembra funzionare : non ci metto proprio lo "__init__" ... spero non sia di disturbo se chiedo un parere sul codice che segue :
# -*- coding: utf-8 -*-

import datetime
import os

class LogsWriter:
    ''' Scrive dei messaggi nel corrente file di log '''
    __instance = None

    def __new__(cls, logdir: str=''):
        if cls.__instance is None:
            cls.__instance = super(LogsWriter, cls).__new__(cls)
            cls.log_dir = logdir
            date = datetime.datetime.now()
            year = date.year
            name = f'Projects_{year}.log'
            f_name = os.path.join(logdir, name)
            cls.logname = f_name
        return cls.__instance
   
    def make_log(self, msg: str) -> None:
        date = datetime.datetime.now()
        m = date.strftime('%Y-%m-%d_%H:%M:%S') + '- ' + msg + '\n'
        with open(self.logname, 'a') as f:
            f.write(m)

Come è evidente è un semplicissimo scrivano per messaggi di log, e con l'implementazione su pare funzionare bene, si inizializza al primo uso ed a istanze successive i riferimenti rimangono e non vengono modificati neanche inserendo nuovi parametri ...
from logswriter2 import LogsWriter
dirname = '/home/nuzzopippo/tmp'
log1 = LogsWriter(dirname)
log1.logname
'/home/nuzzopippo/tmp/Projects_2022.log'
log2 = LogsWriter()
log2.logname
'/home/nuzzopippo/tmp/Projects_2022.log'
log1.make_log('Test da log1')
log2.make_log('Test da log2')
log3 = LogsWriter('/home/nuzzopippo/Documenti')
log3.logname
'/home/nuzzopippo/tmp/Projects_2022.log'

Ci ho dormito poco 'sta notte (a consultare docs) però il risultato che cercavo sembra raggiunto, e sembra che ora posso procedere per singleton più complessi che avevo in mente dato che al momento non vedo controindicazioni ... ma non mi dispiacerebbe un parere più capace sull'approccio.

In ogni caso, grazie ancora :)

Offline GlennHK

  • python sapiens sapiens
  • ******
  • Post: 1.720
  • Punti reputazione: 1
    • Mostra profilo
    • La Tana di GlennHK
Re:Possibile creare singleton con proprietà "statiche"?
« Risposta #3 il: Dicembre 15, 2022, 11:44 »
Non ti risolve 100% il problema, perché magari tu vuoi fare della logica di inizializzazione nell'init.

Io all'epoca feci una roba simile:

def singleton(o_cls):
    orig_new = o_cls.__new__
    inst_field = "_instance"

    @wraps(o_cls.__new__)
    def __new__(cls, *args, **kwargs):
        if (instance := getattr(cls, inst_field, None)) is None:
            instance = orig_new(cls)
            if hasattr(cls, "__init__"):
                cls.__init__(instance, *args, **kwargs)
                delattr(cls, "__init__")
            setattr(cls, inst_field, instance)
        return instance

    o_cls.__new__ = __new__
    return o_cls

Offline nuzzopippo

  • python sapiens
  • *****
  • Post: 670
  • Punti reputazione: 0
    • Mostra profilo
Re:Possibile creare singleton con proprietà "statiche"?
« Risposta #4 il: Dicembre 15, 2022, 12:06 »
Si, in effetti della logica di inizializzazione potrebbe entrarci.

Interessante codice, grazie nuovamente

[Edit] ho atteso, per apporre il "risolto" alla discussione, un po' per riguardare le funzioni built-in relative agli attributi (che uso poco) e, molto di più, per comprendere la notazione ":=", intravista raramente nella docs ma mai usata, naturalmente, la documentazione stessa mi ha chiarito le cose.
Il codice passato da @GlennHK funziona meravigliosamente, il singleton, ora, permette di effettuare una inizializzazione, naturalmente solo e soltanto alla sua prima invocazione.
« Ultima modifica: Dicembre 15, 2022, 16:10 da nuzzopippo »