【问题标题】:Python: thinking of a module and its variables as a singleton — Clean approach?Python:将模块及其变量视为单例——干净的方法?
【发布时间】:2011-06-06 16:25:03
【问题描述】:

我想在我的 Python 程序中实现某种单例模式。我想在不使用类的情况下这样做;也就是说,我想将所有与单例相关的函数和变量放在一个模块中,并认为它是一个真正的单例。

例如,假设这是在文件'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 = ...

然后,程序的其余部分(即其他模块)将像这样使用这个单例:

# 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

这对我来说似乎是个好主意,因为它在使用单例的模块中看起来非常干净。

然而,单例模块中所有这些乏味的“全局”语句都很丑陋。它们出现在处理单例相关变量的每个函数中。在这个特定的示例中,这并不算多,但是当您有 10 多个变量要跨多个函数进行管理时,它就不漂亮了。

此外,如果您碰巧忘记了全局语句,这很容易出错:将创建函数的本地变量,并且不会更改模块的变量,这不是您想要的!

那么,这会被认为是干净的吗?有没有类似于我的方法可以消除“全球”混乱?

或者这根本就不是要走的路?

【问题讨论】:

  • 为什么首先需要一个单例。似乎 90% 认为他们想要/需要单身人士的人真的不这样做。
  • 好吧,我当然并不真正需要单身人士。我曾经使用过一个类,它工作得很好,但我想,既然我只有一个这个特定类的实例,我不能让它成为一个单例吗?
  • 好吧,既然你已经发现了单例的一个问题,我会说班级获胜;)
  • 有人可能会争辩说,如果你确实需要一个单例 - 它的状态越少越好。如果你最终得到太多globals,也许这也是糟糕设计的标志?另见stackoverflow.com/questions/137975/… :)

标签: python module singleton


【解决方案1】:

将模块用作单例的常见替代方法是 Alex Martelli 的 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!

这个类可以有多个实例,但它们都共享相同的状态。

【讨论】:

  • Alex 似乎已经从 SO 中消失了——自去年 11 月以来他就再也没有出现过! 8v(
  • 你的意思是多个变量引用同一个实例还是多个实例都共享相同的状态?你能澄清一下吗?
  • @Jaydev 我的意思是我所说的——多个实例共享相同的状态。 __shared_state 字典是一个类变量,所以类的所有实例共享它们的状态。
【解决方案2】:

也许你可以把所有的变量放在一个全局字典中,你可以直接在你的函数中使用字典而不用“全局”。

# 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'] = ...

为什么你可以这样做是Python Scopes and Namespaces

【讨论】:

    【解决方案3】:

    使用 Python 实现单例模式的一种方法也可以是:

    如果类的实例已经存在,则单例__init()__ 方法会引发异常。更准确地说,类有一个成员_single。如果此成员与None 不同,则会引发异常。

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

    可以说,处理带有异常的单例实例创建也不是很干净。我们可以使用 handle() 方法隐藏实现细节,如

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

    Handle() 方法与单例模式的 C++ 实现非常相似。我们可以在Singleton 类中使用handle()

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

    返回一个新的Singleton 实例或对Singleton 类的现有唯一实例的引用。

    处理整个层次结构

    如果Single1Single2 类派生自Singleton,则存在通过派生类之一的Singleton 的单个实例。这可以用这个来验证:

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

    【讨论】:

      【解决方案4】:

      与 Sven 的“Borg 模式”建议类似,您可以将所有状态数据保存在一个类中,而无需创建该类的任何实例。我相信这种方法利用了新型类。

      这种方法甚至可以适应 Borg 模式,但需要注意的是,从类的实例修改状态成员需要访问实例的 __class__ 属性(instance.__class__.foo = 'z' 而不是 instance.foo = 'z',不过你也可以只做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)
      

      请注意,即使在实例化之后,对类属性的修改也会反映在类的实例中。这包括添加新属性和删除现有属性,这可能会产生一些有趣的、可能有用的效果(尽管我也可以看到在某些情况下这实际上可能会导致问题)。自己试试吧。

      【讨论】:

        【解决方案5】:

        WillYang's answer 的基础上更进一步,更简洁:定义一个简单的类来保存您的全局字典,使其更易于引用:

        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)
        

        就像在g 中放入任何你想要的东西之前一样,但现在它超级干净。 :)

        【讨论】:

          【解决方案6】:

          对于合法的单身人士:

          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!'
          

          要查看它确实是一个单例,请尝试实例化此类或任何继承自它的类。

          singleton = Singleton() # prints "hi!"
          

          【讨论】:

            猜你喜欢
            • 2023-01-09
            • 2016-07-05
            • 2018-02-16
            • 2011-11-08
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-12-27
            相关资源
            最近更新 更多