开发者

Python: thinking of a module and its variables as a singleton — Clean approach?

I'd like to implement some sort of singleton pattern in my Python program. I was thinking of doing it without using classes; that is, I'd like to put all the singleton-related functions and variables wit开发者_C百科hin a module and consider it an actual singleton.

For example, say this is to be in the file 'singleton_module.py':

# singleton_module.py

# Singleton-related variables
foo = 'blah'
bar = 'stuff'

# Functions that process the above variables
def work(some_parameter):
    global foo, bar
    if some_parameter:
        bar = ...
    else:
        foo = ...

Then, the rest of the program (i.e., other modules) would use this singleton like so:

# another_module.py

import singleton_module

# process the singleton variables,
# which changes them across the entire program
singleton_module.work(...)

# freely access the singleton variables
# (at least for reading)
print singleton_module.foo

This seemed to be a pretty good idea to me, because it looks pretty clean in the modules that use the singleton.

However, all these tedious 'global' statements in the singleton module are ugly. They occur in every function that processes the singleton-related variables. That's not much in this particular example, but when you have 10+ variables to manage across several functions, it's not pretty.

Also, this is pretty error-prone if you happen to forget the global statements: variables local to the function will be created, and the module's variables won't be changed, which is not what you want!

So, would this be considered to be clean? Is there an approach similar to mine that manages to do away with the 'global' mess?

Or is this simply not the way to go?


A common alternative to using a module as a singleton is Alex Martelli's Borg pattern:

class Borg:
    __shared_state = {}
    def __init__(self):
        self.__dict__ = self.__shared_state
    # and whatever else you want in your class -- that's all!

There can be multiple instances of this class, but they all share the same state.


Maybe you can put all the variables in a global dict, and you can directly use the dict in your functions without "global".

# Singleton-related variables
my_globals = {'foo': 'blah', 'bar':'stuff'}

# Functions that process the above variables
def work(some_parameter):
    if some_parameter:
        my_globals['bar'] = ...
    else:
        my_globals['foo'] = ...

why you can do it like this is Python Scopes and Namespaces.


One approach to implementing a singleton pattern with Python can also be:

have singleton __init()__ method raise an exception if an instance of the class already exists. More precisely, class has a member _single. If this member is different from None, exception is raised.

class Singleton:
    __single = None
    def __init__( self ):
        if Singleton.__single:
            raise Singleton.__single
        Singleton.__single = self  

It could be argued that handling the singleton instance creation with exceptions is not very clean also. We may hide implementation details with a method handle() as in

def Handle( x = Singleton ):
    try:
        single = x()
    except Singleton, s:
        single = s
    return single 

this Handle() method is very similar to what would be a C++ implementation of the Singleton pattern. We could have in Singleton class the handle()

Singleton& Singleton::Handle() {

  if( !psingle ) {
    psingle = new Singleton;
  }
  return *psingle;
}

returning either a new Singleton instance or a reference to the existing unique instance of class Singleton.

Handling the whole hierarchy

If Single1 and Single2 classes derive from Singleton, a single instance of Singleton through one of the derived class exists. This can be verify with this:

>>> child = S2( 'singlething' )
>>> junior = Handle( S1)
>>> junior.name()
'singlething'


Similar to Sven's "Borg pattern" suggestion, you could just keep all your state data in a class, without creating any instances of the class. This method utilizes new-style classes, I believe.

This method could even be adapted into the Borg pattern, with the caveat that modifying the state members from the instances of the class would require accessing the __class__ attribute of the instance (instance.__class__.foo = 'z' rather than instance.foo = 'z', though you could also just do stateclass.foo = 'z').

class State:    # in some versions of Python, may need to be "class State():" or "class State(object):"
    __slots__ = []   # prevents additional attributes from being added to instances and same-named attributes from shadowing the class's attributes
    foo = 'x'
    bar = 'y'

    @classmethod
    def work(cls, spam):
        print(cls.foo, spam, cls.bar)

Note that modifications to the class's attributes will be reflected in instances of the class even after instantiation. This includes adding new attributes and removing existing ones, which could have some interesting, possibly useful effects (though I can also see how that might actually cause problems in some cases). Try it out yourself.


Building off of WillYang's answer and taking it a step further for cleanliness: define a simple class to hold your global dictionary to make it easier to reference:

class struct(dict):
    def __init__(self, **kwargs):
        dict.__init__(self, kwargs)
        self.__dict__ = self

g = struct(var1=None, var2=None)

def func():
    g.var1 = dict()
    g.var3 = 10
    g["var4"] = [1, 2]
    print(g["var3"])
    print(g.var4)

Just like before you put anything you want in g but now it's super clean. :)


For a legitimate Singleton:

class SingletonMeta(type):
    __classes = {} # protect against defining class with the same name
    def __new__(cls, cls_name, cls_ancestors, cls_dict): 
         if cls_name in cls.__classes:
             return cls.__classes[cls_name]
         type_instance = super(SingletonMeta, cls).__new__(cls, cls_name, cls_ancestors, cls_dict) # pass 'type' instead of 'cls' if you dont want SingletonMeta's attributes reflected in the class
         return type_instance() # call __init__

class Singleton:
    __metaclass__ = SingletonMeta
    # define __init__ however you want

    __call__(self, *args, *kwargs):
        print 'hi!'

To see that it truly is a singleton, try to instantiate this class, or any class that inherits from it.

singleton = Singleton() # prints "hi!"
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜