È ragionevole usare un decoratore per istanziare un singleton in Python?

3

Supponiamo di avere una classe come questa:

class Foo(object):
    # some code here

Come succede, Foo è un singleton. Esistono numerosi modi per scrivere singleton in Python, ma la maggior parte di loro non mi sembra davvero molto pitone. Se qualcun altro ha una ragione particolarmente valida per creare istanze o sottoclassi della mia classe, non vedo alcun motivo per non rendere le cose irragionevolmente difficili per loro (oltre a un minimo "questo non fa parte dell'API pubblica e potrebbe rompere tutto" suggerimento).

Quindi mi sono imbattuto in questa idea:

def singleton(cls):
    return cls()

@singleton
class foo_instance(object):  # note lower case
    # some code here

Ho dovuto scrivere singleton() perché operator.call() non è una cosa per qualche strana ragione.

Ovviamente, puoi ancora fare type(foo_instance) e creare istanze manualmente, ma la mancanza di una classe visibile pubblicamente rende evidente (per me) che non sei supposto per farlo (più o meno allo stesso modo in cui non dovresti scherzare con type(enum.Enum) in 3.4+), e penso che sia più pulito di una classe "privata" con prefisso sottolineato come class _Foo .

Questo è un modo ragionevole per fare una lezione singolare?

    
posta Kevin 20.02.2015 - 06:05
fonte

2 risposte

1

Penso che il modo più semplice e più Pythonic per implementare una classe singleton sia con l'ereditarietà:

class Singleton(object):

    _instances = {}

    def __new__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = object.__new__(cls, *args, **kwargs)
        return cls._instances[cls]


class Foo(Singleton):  # Foo 'is a' Singleton
    pass

Ora l'utilizzo è semplice:

>>> foo1 = Foo()
>>> foo2 = Foo()
>>> foo1 is foo2
True

e non si interromperà nel caso in cui identificate in precedenza:

>>> foo3 = type(foo1)()
>>> foo1 is foo3
True

Puoi anche ereditare da Foo ; anche la classe figlio sarà Singleton .

Un problema da tenere presente è che se implementi Foo.__init__ , verrà chiamato ogni volta che Foo() viene chiamato . Se ciò non è auspicabile, sposta l'inizializzazione al costruttore (ad esempio implementa Foo.__new__ , ricordando di chiamare anche il costruttore super ).

    
risposta data 20.02.2015 - 11:40
fonte
1

Sì! È assolutamente ragionevole ... (penserei)

L'ho "inventato" (probabilmente dovrei dire "re-inventato") anch'io da molto tempo, e lo uso ancora dove serve. Qui, "dove necessario" di solito significa "dove rende l'intenzione più chiara" e non "dove voglio essere sicuro che nessuno si prenda a una seconda istanza di quella classe".

In realtà trovo la soluzione fornita da @jonrsharpe per non essere molto Pythonic. È sicuramente un bel trucco! Anche se non vuoi farlo attraverso l'ereditarietà ma tramite un decoratore, l'idea generale può essere applicata. Ma un costruttore che restituisce un'istanza esistente non mi sembra affatto Pythoniano.

Quindi, sì, nascondere semplicemente la classe dietro l'unica istanza fa tutto ciò che è destinato. Il fatto che type(foo_instance)() crei una nuova istanza non toglie nulla. Se uno era obbligato a fare cose simili con altri codici Python, era banalmente semplice farlo. Altrimenti Python dovrebbe anche vietare l'accesso esterno ai metodi di sottolineatura, ecc. Quindi, si riduce a " siamo tutti adulti consenzienti qui ".

Nota: so che questa domanda è piuttosto vecchia, ma l'ho appena trovata quando cercavo un modo migliore (più pitone?) per raggiungere lo stesso per curiosità. Ma le alternative (anche se sono intelligenti e funzionano bene), non sembrano essere migliori. Quindi, il design semplice è probabilmente il migliore ...

    
risposta data 05.10.2017 - 00:48
fonte

Leggi altre domande sui tag