Google App Engine 单例模式 (Python)

2 投票
4 回答
2327 浏览
提问于 2025-04-15 17:54

在Python中,创建单例模式的标准方法是

class Singleton(object):
    _instance = None
    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
        return cls._instance

但是,这种方法在App Engine上不太适用,因为可能会有很多服务器,每个服务器上都会有一个实例。那么我们该如何为App Engine的实体实现单例呢?

可以尝试这样的方式:

class MySingleton(db.models):
    def __init__(self):
        all = MySingleton.all()
        if all.count() > 0:
             return all.fetch(1).get()

        super(MySingleton, self).__init__ (*args, **kwargs)

不过这样会导致递归错误,因为get()会调用__init__

我们将如何使用它

我们只是想表示一个配置文件,比如:

{ 'sitename': "My site", 'footer': "This page owned by X"}

4 个回答

1

__init__ 这个方法其实不能有效地 return 任何东西。就像第一个例子一样,应该重写 __new__ 方法!

4

单例模式通常不是个好主意,我很想知道为什么这个例子会例外。一般来说,单例模式就像是伪装的全局变量,除了全局变量带来的各种老问题(比如可以参考http://c2.com/cgi/wiki?GlobalVariablesAreBad,特别是开头提到的非局部性、隐式耦合、并发问题,以及测试和隔离),在现代的环境下,还会因为分布式和并发系统带来额外的问题。如果你的应用可能在多个服务器上运行,能否安全且正确地让两个应用实例操作同一个单例实例呢?

如果这个对象没有自己的状态,那么答案是可以的,但你不需要单例,只需要一个命名空间就行了。

但是如果这个对象有状态,你就需要考虑这两个应用实例如何保持信息同步。如果两个实例同时尝试读取然后写入同一个实例,那么结果很可能会出错。(比如,一个计数器单例读取当前值,加1,然后写回当前值,这样可能会漏掉一些计数——这也是我能想到的最轻微的例子。)

我对这个不是很熟悉,也许谷歌的应用引擎有一些事务逻辑可以帮你处理这些问题,但这可能意味着你还得添加一些额外的东西来处理回滚等情况。

所以我基本的建议是,看看能否在不使用单例的情况下重写算法或系统。

2

如果你不打算把数据存储在数据库里,那为什么不直接创建一个包含变量的模块,而不是用db.Model呢?

你可以把文件命名为mysettings.py,然后在里面写:

sitename = "My site"
footer = "This page owned by X"

这样,Python模块就变成了一个“单例”。如果需要的话,你甚至可以添加一些函数。使用的时候,可以这样做:

import mysettings
print mysettings.sitename

这就是Django处理这个问题的方式,使用他们的DJANGO_SETTINGS_MODULE

更新
听起来你其实想用db.Model,但又想用memcached,这样你就只需要获取一次对象。不过,当你更改数据时,你得想办法清除缓存,或者设置一个超时时间,让它偶尔被获取。我可能会选择超时的方式,然后在mysettings.py里做类似这样的事情:

from google.appengine.api import memcache
class MySettings(db.Model):
   # properties...

def Settings():
    key = "mysettings"
    obj = memcache.get(key)
    if obj is None:
       obj = MySettings.all().get()  # assume there is only one
       if obj:
            memcache.add(key, zone, 360)
       else:
            logging.error("no MySettings found, create one!")
    return obj

或者,如果你不想用memcache,那就把对象存储在模块级别的变量里,并始终使用Settings()函数来引用它。但这样你得想办法在解释器实例被回收之前清除它。我通常会用memcached来实现这种功能。

撰写回答