最近在重构项目代码, 有个需求是需要声明一个变量, 然后任何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对象, 然后放到ManagerloggerDict中进行管理.

当你在别的地方也声明了这个name的logger时, Manager会发现loggerDict中已经有了, 直接拿出来用就行了. 其中还包括一些日志层级, hierarchy那些东西的处理, 不过这不是本文重点.

至于线程安全, 很简单, 代码里给加了线程锁. 这样打日志就不会东一块西一块了, 可惜多进程的日志标准库没有给进程安全的实现, 当然自己统一上传用socket处理或者用个process queue处理都是可以的.

如果想在允许多次实例化的情况下, 实现这种解释器级别变量呢?
那就是单例模式了, 在实例已经存在时调用一个函数对自己进行重载或更新即可


quietin
761 声望44 粉丝

兴趣在程序语言, 高性能, 分布式等方面