最近在重构项目代码, 有个需求是需要声明一个变量, 然后任何import
了这个变量的模块, 当这个变量在其它地方更改了值以后, 这个变化都能反映出来, 乍看好像有点麻烦, 其实很简单.
就通常的想法来讲, 你用Dict
, list
或一个类实例都能实现. 因为在使用它们时, 本质上还是一种引用的方式, 而不是像其它变量是值复制, 所以它们的修改或变化是肯定能反映出来的
比如tornado的options
, 没记错的话, 文档里写的什么解释器级别变量, 很高端的样子, 其实实现不难, 直接跳到代码
options = OptionParser()
"""Global options object.
All defined options are available as attributes on this object.
"""
Global的对象, 因为OptionParser
的实例就一个options
, 每次使用是怎么使用的呢
from tornado.options import options
每次都import的是这个实例, 而包括define
在内的函数, 其实都是在对这个唯一的实例在进行操作而已, 代码中有这段
def define(name, default=None, type=None, help=None, metavar=None,
multiple=False, group=None, callback=None):
"""Defines an option in the global namespace.
See `OptionParser.define`.
"""
return options.define(name, default=default, type=type, help=help,
metavar=metavar, multiple=multiple, group=group,
callback=callback)
另外一个令人容易想到的解释器级别的东西就是logger, logging.getLogger()
也是声明解释器级别的对象, 而且还是线程安全的. 我们只需要在某个模块中声明一个logger
, 然后在其它模块里
import logging
logger = logging.getLogger('xxname')
就行了
直接看代码
def getLogger(self, name):
"""
Get a logger with the specified name (channel name), creating it
if it doesn't yet exist. This name is a dot-separated hierarchical
name, such as "a", "a.b", "a.b.c" or similar.
If a PlaceHolder existed for the specified name [i.e. the logger
didn't exist but a child of it did], replace it with the created
logger and fix up the parent/child references which pointed to the
placeholder to now point to the logger.
"""
rv = None
if not isinstance(name, basestring):
raise TypeError('A logger name must be string or Unicode')
if isinstance(name, unicode):
name = name.encode('utf-8')
_acquireLock()
try:
if name in self.loggerDict:
rv = self.loggerDict[name]
if isinstance(rv, PlaceHolder):
ph = rv
rv = (self.loggerClass or _loggerClass)(name)
rv.manager = self
self.loggerDict[name] = rv
self._fixupChildren(ph, rv)
self._fixupParents(rv)
else:
rv = (self.loggerClass or _loggerClass)(name)
rv.manager = self
self.loggerDict[name] = rv
self._fixupParents(rv)
finally:
_releaseLock()
return rv
这个getLogger
函数最终调用的是Manager
类的实例函数getLogger
, 当你用的这个name不存在时, 通常是日志初始化时, 会声明一个Logger
对象, 然后放到Manager
的loggerDict
中进行管理.
当你在别的地方也声明了这个name的logger时, Manager
会发现loggerDict
中已经有了, 直接拿出来用就行了. 其中还包括一些日志层级, hierarchy那些东西的处理, 不过这不是本文重点.
至于线程安全, 很简单, 代码里给加了线程锁. 这样打日志就不会东一块西一块了, 可惜多进程的日志标准库没有给进程安全的实现, 当然自己统一上传用socket处理或者用个process queue处理都是可以的.
如果想在允许多次实例化的情况下, 实现这种解释器级别变量呢?
那就是单例模式了, 在实例已经存在时调用一个函数对自己进行重载或更新即可
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。