Google App Engine 单例模式 (Python)
在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 个回答
__init__
这个方法其实不能有效地 return
任何东西。就像第一个例子一样,应该重写 __new__
方法!
单例模式通常不是个好主意,我很想知道为什么这个例子会例外。一般来说,单例模式就像是伪装的全局变量,除了全局变量带来的各种老问题(比如可以参考http://c2.com/cgi/wiki?GlobalVariablesAreBad,特别是开头提到的非局部性、隐式耦合、并发问题,以及测试和隔离),在现代的环境下,还会因为分布式和并发系统带来额外的问题。如果你的应用可能在多个服务器上运行,能否安全且正确地让两个应用实例操作同一个单例实例呢?
如果这个对象没有自己的状态,那么答案是可以的,但你不需要单例,只需要一个命名空间就行了。
但是如果这个对象有状态,你就需要考虑这两个应用实例如何保持信息同步。如果两个实例同时尝试读取然后写入同一个实例,那么结果很可能会出错。(比如,一个计数器单例读取当前值,加1,然后写回当前值,这样可能会漏掉一些计数——这也是我能想到的最轻微的例子。)
我对这个不是很熟悉,也许谷歌的应用引擎有一些事务逻辑可以帮你处理这些问题,但这可能意味着你还得添加一些额外的东西来处理回滚等情况。
所以我基本的建议是,看看能否在不使用单例的情况下重写算法或系统。
如果你不打算把数据存储在数据库里,那为什么不直接创建一个包含变量的模块,而不是用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来实现这种功能。