python重复import_python中的module不⼀定是单例,有可能
会被多次im。。。
引⾔
作为⼀个python新⼿,我偶尔会遇到⼀些奇怪的问题。
最近遇到⼀个奇怪的问题,python中的logging为什么会输出重复⽇志?
经过排查发现,python中的module并不是只被import⼀次的,从⽽导致我们的logger组件会输出重复⽇志。
所以说: 基于module import机制的单例是不可靠的,有可能会被多次import从⽽创建出多个实例。
下⾯简单和⼤家分享⼀下。
问题描述
之前看到过⽂章,说python中的包只会被import⼀次,所以可以⽤module内的全局变量来实现单例。 参考链接:Python单例模式终极版但是我的这个case发现并不是如此,我们有⼀个logger.py放在log这个
⽬录下,然后外部有两个py⽂件对它进⾏import。代码结构如下:.
├── README.md
├── log
│ ├── __init__.py
│ └── logger.py
├── main.py
└── util.py
其中logger.py的代码如下:
import logging
import logging.handlers
def get_logger():
logger = Logger("a")
logger.setLevel(logging.DEBUG)
stdout_handler = logging.StreamHandler()
logger.addHandler(stdout_handler)
return logger
logger = get_logger()
我们有理由相信,不管logger这个模块被import⼏次,logger = get_logger()这⾏代码只会执⾏⼀次,只会有⼀个logger实例。
然后,util.py和main.py都会import这个logger,代码如下
util.py:
from logger import logger
def util_a():
pass
main.py:
from util import util_a
from log.logger import logger
if __name__ == '__main__':
logger.debug("test loggger.debug")
我们认为,运⾏main.py将输出⼀⾏⽇志,test loggger.debug。 然⽽,实际运⾏main.py,结果是两⾏⽇志:
$ python3 main.py
test loggger.debug
test loggger.debug
这是怎么回事呢?
问题原因
经过排查代码(实际项⽬远⽐这个demo复杂,所以排查代码也不容易),我们发现,代码中存在两种import⽅式:
from logger import logger
from log.logger import logger
很可能是这两种⽅式import导致的。将这两种import改成⼀致,⽇志就只有⼀⾏了。
之所以可以⽤两种⽅式import, 是因为我们项⽬中,将log这个路径加⼊到了sys.path中。所以两种⽅式都可以搜索到。
这两种⽅式import的是同⼀个python⽂件,为什么python会import两次呢?难道python没有智能地判断这两种不同的import写法,其实是import同⼀个.py⽂件吗?
我们来验证⼀下,我们知道,所有import的module,都在dules⾥⾯,所以将它输出来看⼀下,考虑到module众多,所以只过滤出logger相关的module,代码如下:
log_modules={k:v for k,v dules.items() if k.find("logger")!=-1}
print("log_modules=",log_modules)
运⾏⼀下,得到结果:
log_modules= {'logger': ,
'log.logger': }
果然,我们发现dules含有 logger和 log.logger 这两个module,这两个module分别import了⼀次,从⽽调⽤了两次
get_logger()这个函数。 ⽽两次Logger会返回同⼀个logger对象,只是增加了两个handler,从⽽输出了两⾏重复的⽇志。
所以基于module import机制的单例是不可靠的。假如我们这⾥不是logger组件,⽽是别的单例组件,那么我们这两种不同的import⽅式,就会产⽣两个实例,从⽽保证不了单例。
解决⽅案
查到了原因,就很容易解决。 ⾸先,我们把所有import都改成同⼀种写法,
from log.logger import logger
其次,保不准别⼈新增代码的时候,还会使⽤不同的import⽅式,所以需要在logger.py组件中加上保护,要保证就算多次impot仍然是幂等的,不会有什么副作⽤。
这种做法,当然就是module⾥⾯只声明class和function,不定义任何变量也不调⽤任何函数,⾃然就不会有什么副作⽤了。
当然,针对logger这个特殊情况,我们可以简单地对logger.handlers加⼀个判断就可以了:
def get_logger():
logger = Logger("a")
if logger.handlers:
return logger
logger.setLevel(logging.DEBUG)
单例模式的几种实现方式stdout_handler = logging.StreamHandler()
logger.addHandler(stdout_handler)
return logger
改完之后,就ok了,⽇志只有⼀⾏了。
经验总结基于module import机制的单例是不可靠的,有可能会被多次import从⽽创建出多个实例,需要注意加上幂等性保护⼀个module应该只有⼀种⽅式import,这也是python的哲学,所以项⽬中的⼦⽬录不要加⼊到sys.path中
⽰例代码