六 模块搜索路径
模块的查找顺序是:内存中已经加载的模块->内置模块->sys.path路径中包含的模块
#模块的查找顺序 1、在第一次导入某个模块时(比如spam),会先检查该模块是否已经被加载到内存中(当前执行文件的名称空间对应的内存),如果有则直接引用 ps:python解释器在启动时会自动加载一些模块到内存中,可以使用sys.modules查看 2、如果没有,解释器则会查找同名的内建模块 3、如果还没有找到就从sys.path给出的目录列表中依次寻找spam.py文件。 #sys.path的初始化的值来自于: The directory containing the input script (or the current directory when no file is specified). PYTHONPATH (a list of directory names, with the same syntax as the shell variable PATH). The installation-dependent default. #需要特别注意的是:我们自定义的模块名不应该与系统内置模块重名。虽然每次都说,但是仍然会有人不停的犯错。 #在初始化后,python程序可以修改sys.path,路径放到前面的优先于标准库被加载。 1 >>> import sys 2 >>> sys.path.append('/a/b/c/d') 3 >>> sys.path.insert(0,'/x/y/z') #排在前的目录,优先被搜索 注意:搜索时按照sys.path中从左到右的顺序查找,位于前的优先被查找,sys.path中还可能包含.zip归档文件和.egg文件,python会把.zip归档文件当成一个目录去处理, #首先制作归档文件:zip module.zip foo.py bar.py import sys sys.path.append('module.zip') import foo,bar #也可以使用zip中目录结构的具体位置 sys.path.append('module.zip/lib/python') #windows下的路径不加r开头,会语法错误 sys.path.insert(0,r'C:UsersAdministratorPycharmProjectsa') #至于.egg文件是由setuptools创建的包,这是按照第三方python库和扩展时使用的一种常见格式,.egg文件实际上只是添加了额外元数据(如版本号,依赖项等)的.zip文件。 #需要强调的一点是:只能从.zip文件中导入.py,.pyc等文件。使用C编写的共享库和扩展块无法直接从.zip文件中加载(此时setuptools等打包系统有时能提供一种规避方法),且从.zip中加载文件不会创建.pyc或者.pyo文件,因此一定要事先创建他们,来避免加载模块是性能下降。
#模块的查找顺序 1、在第一次导入某个模块时(比如spam),会先检查该模块是否已经被加载到内存中(当前执行文件的名称空间对应的内存),如果有则直接引用 ps:python解释器在启动时会自动加载一些模块到内存中,可以使用sys.modules查看 2、如果没有,解释器则会查找同名的内建模块 3、如果还没有找到就从sys.path给出的目录列表中依次寻找spam.py文件。 #sys.path的初始化的值来自于: The directory containing the input script (or the current directory when no file is specified). PYTHONPATH (a list of directory names, with the same syntax as the shell variable PATH). The installation-dependent default. #需要特别注意的是:我们自定义的模块名不应该与系统内置模块重名。虽然每次都说,但是仍然会有人不停的犯错。 #在初始化后,python程序可以修改sys.path,路径放到前面的优先于标准库被加载。 1 >>> import sys 2 >>> sys.path.append('/a/b/c/d') 3 >>> sys.path.insert(0,'/x/y/z') #排在前的目录,优先被搜索 注意:搜索时按照sys.path中从左到右的顺序查找,位于前的优先被查找,sys.path中还可能包含.zip归档文件和.egg文件,python会把.zip归档文件当成一个目录去处理, #首先制作归档文件:zip module.zip foo.py bar.py import sys sys.path.append('module.zip') import foo,bar #也可以使用zip中目录结构的具体位置 sys.path.append('module.zip/lib/python') #windows下的路径不加r开头,会语法错误 sys.path.insert(0,r'C:UsersAdministratorPycharmProjectsa') #至于.egg文件是由setuptools创建的包,这是按照第三方python库和扩展时使用的一种常见格式,.egg文件实际上只是添加了额外元数据(如版本号,依赖项等)的.zip文件。 #需要强调的一点是:只能从.zip文件中导入.py,.pyc等文件。使用C编写的共享库和扩展块无法直接从.zip文件中加载(此时setuptools等打包系统有时能提供一种规避方法),且从.zip中加载文件不会创建.pyc或者.pyo文件,因此一定要事先创建他们,来避免加载模块是性能下降。
官网解释:
#官网链接:https://docs.python.org/3/tutorial/modules.html#the-module-search-path 搜索路径: 当一个命名为spam的模块被导入时 解释器首先会从内建模块中寻找该名字 找不到,则去sys.path中找该名字 sys.path从以下位置初始化 1 执行文件所在的当前目录 2 PTYHONPATH(包含一系列目录名,与shell变量PATH语法一样) 3 依赖安装时默认指定的 注意:在支持软连接的文件系统中,执行脚本所在的目录是在软连接之后被计算的,换句话说,包含软连接的目录不会被添加到模块的搜索路径中 在初始化后,我们也可以在python程序中修改sys.path,执行文件所在的路径默认是sys.path的第一个目录,在所有标准库路径的前面。这意味着,当前目录是优先于标准库目录的,需要强调的是:我们自定义的模块名不要跟python标准库的模块名重复,除非你是故意的,傻叉。
七 编译python文件(了解)
为了提高加载模块的速度,强调强调强调:提高的是加载速度而绝非运行速度。python解释器会在__pycache__目录中下缓存每个模块编译后的版本,格式为:module.version.pyc。通常会包含python的版本号。例如,在CPython3.3版本下,spam.py模块会被缓存成__pycache__/spam.cpython-33.pyc。这种命名规范保证了编译后的结果多版本共存。
Python检查源文件的修改时间与编译的版本进行对比,如果过期就需要重新编译。这是完全自动的过程。并且编译的模块是平台独立的,所以相同的库可以在不同的架构的系统之间共享,即pyc使一种跨平台的字节码,类似于JAVA火.NET,是由python虚拟机来执行的,但是pyc的内容跟python的版本相关,不同的版本编译后的pyc文件不同,2.5编译的pyc文件不能到3.5上执行,并且pyc文件是可以反编译的,因而它的出现仅仅是用来提升模块的加载速度的,不是用来加密的。
#python解释器在以下两种情况下不检测缓存 #1 如果是在命令行中被直接导入模块,则按照这种方式,每次导入都会重新编译,并且不会存储编译后的结果(python3.3以前的版本应该是这样) python -m spam.py #2 如果源文件不存在,那么缓存的结果也不会被使用,如果想在没有源文件的情况下来使用编译后的结果,则编译后的结果必须在源目录下 sh-3.2# ls __pycache__ spam.py sh-3.2# rm -rf spam.py sh-3.2# mv __pycache__/spam.cpython-36.pyc ./spam.pyc sh-3.2# python3 spam.pyc spam #提示: 1.模块名区分大小写,foo.py与FOO.py代表的是两个模块 2.你可以使用-O或者-OO转换python命令来减少编译模块的大小 -O转换会帮你去掉assert语句 -OO转换会帮你去掉assert语句和__doc__文档字符串 由于一些程序可能依赖于assert语句或文档字符串,你应该在在确认需要 的情况下使用这些选项。 3.在速度上从.pyc文件中读指令来执行不会比从.py文件中读指令执行更快,只有在模块被加载时,.pyc文件才是更快的 4.只有使用import语句是才将文件自动编译为.pyc文件,在命令行或标准输入中指定运行脚本则不会生成这类文件,因而我们可以使用compieall模块为一个目录中的所有模块创建.pyc文件 模块可以作为一个脚本(使用python -m compileall)编译Python源 python -m compileall /module_directory 递归着编译 如果使用python -O -m compileall /module_directory -l则只一层 命令行里使用compile()函数时,自动使用python -O -m compileall 详见:https://docs.python.org/3/library/compileall.html#module-compileall
八 包介绍
1、什么是包?
#官网解释 Packages are a way of structuring Python’s module namespace by using “dotted module names” 包是一种通过使用‘.模块名’来组织python模块名称空间的方式。 #具体的:包就是一个包含有__init__.py文件的文件夹,所以其实我们创建包的目的就是为了用文件夹将文件/模块组织起来 #需要强调的是: 1. 在python3中,即使包下没有__init__.py文件,import 包仍然不会报错,而在python2中,包下一定要有该文件,否则import 包报错 2. 创建包的目的不是为了运行,而是被导入使用,记住,包只是模块的一种形式而已,包的本质就是一种模块
2、为何要使用包
包的本质就是一个文件夹,那么文件夹唯一的功能就是将文件组织起来 随着功能越写越多,我们无法将所以功能都放到一个文件中,于是我们使用模块去组织功能,而随着模块越来越多,我们就需要用文件夹将模块文件组织起来,以此来提高程序的结构性和可维护性
3、注意事项
#1.关于包相关的导入语句也分为import和from ... import ...两种,但是无论哪种,无论在什么位置,在导入时都必须遵循一个原则:凡是在导入时带点的,点的左边都必须是一个包,否则非法。可以带有一连串的点,如item.subitem.subsubitem,但都必须遵循这个原则。但对于导入后,在使用时就没有这种限制了,点的左边可以是包,模块,函数,类(它们都可以用点的方式调用自己的属性)。 #2、import导入文件时,产生名称空间中的名字来源于文件,import 包,产生的名称空间的名字同样来源于文件,即包下的__init__.py,导入包本质就是在导入该文件 #3、包A和包B下有同名模块也不会冲突,如A.a与B.a来自俩个命名空间
4、上课流程
1 实验一 准备: 执行文件为test.py,内容 #test.py import aaa 同级目录下创建目录aaa,然后自建空__init__.py(或者干脆建包) 需求:验证导入包就是在导入包下的__init__.py 解决: 先执行看结果 再在__init__.py添加打印信息后,重新执行 2、实验二 准备:基于上面的结果 需求: aaa.x aaa.y 解决:在__init__.py中定义名字x和y 3、实验三 准备:在aaa下建立m1.py和m2.py #m1.py def f1(): print('from 1') #m2.py def f2(): print('from 2') 需求: aaa.m1 #进而aaa.m1.func1() aaa.m2 #进而aaa.m2.func2() 解决:在__init__.py中定义名字m1和m2,先定义一个普通变量,再引出如何导入模块名,强调:环境变量是以执行文件为准 4、实验四 准备:在aaa下新建包bbb 需求: aaa.bbb 解决:在aaa的__init__.py内导入名字bbb 5、实验五 准备: 在bbb下建立模块m3.py #m3.py def f3(): print('from 3') 需求: aaa.bbb.m3 #进而aaa.bbb.m3.f3() 解决:是bbb下的名字m3,因而要在bbb的__init__.py文件中导入名字m3,from aaa.bbb import m3 6、实验六 准备:基于上面的结果 需求: aaa.m1() aaa.m2() aaa.m3() 进而实现 aaa.f1() aaa.f2() aaa.f3() 先用绝对导入,再用相对导入 解决:在aaa的__init__.py中拿到名字m1、m2、m3 包内模块直接的相对导入,强调包的本质:包内的模块是用来被导入的,而不是被执行的 用户无法区分模块是文件还是一个包,我们定义包是为了方便开发者维护 7、实验七 将包整理当做一个模块,移动到别的目录下,操作sys.path
九 包的使用
1、示范文件
glance/ #Top-level package ├── __init__.py #Initialize the glance package ├── api #Subpackage for api │ ├── __init__.py │ ├── policy.py │ └── versions.py ├── cmd #Subpackage for cmd │ ├── __init__.py │ └── manage.py └── db #Subpackage for db ├── __init__.py └── models.py
#文件内容 #policy.py def get(): print('from policy.py') #versions.py def create_resource(conf): print('from version.py: ',conf) #manage.py def main(): print('from manage.py') #models.py def register_models(engine): print('from models.py: ',engine) 包所包含的文件内容
#文件内容 #policy.py def get(): print('from policy.py') #versions.py def create_resource(conf): print('from version.py: ',conf) #manage.py def main(): print('from manage.py') #models.py def register_models(engine): print('from models.py: ',engine) 包所包含的文件内容
执行文件与示范文件在同级目录下
2、包的使用之import
1 import glance.db.models 2 glance.db.models.register_models('mysql')
单独导入包名称时不会导入包中所有包含的所有子模块,如
#在与glance同级的test.py中 import glance glance.cmd.manage.main() ''' 执行结果: AttributeError: module 'glance' has no attribute 'cmd' '''
解决方法:
1 #glance/__init__.py 2 from . import cmd 3 4 #glance/cmd/__init__.py 5 from . import manage
执行:
1 #在于glance同级的test.py中 2 import glance 3 glance.cmd.manage.main()
3、包的使用之from ... import ...
需要注意的是from后import导入的模块,必须是明确的一个不能带点,否则会有语法错误,如:from a import b.c是错误语法
1 from glance.db import models 2 models.register_models('mysql') 3 4 from glance.db.models import register_models 5 register_models('mysql')
4、from glance.api import *
在讲模块时,我们已经讨论过了从一个模块内导入所有*,此处我们研究从一个包导入所有*。
此处是想从包api中导入所有,实际上该语句只会导入包api下__init__.py文件中定义的名字,我们可以在这个文件中定义__all___:
1 #在__init__.py中定义 2 x=10 3 4 def func(): 5 print('from api.__init.py') 6 7 __all__=['x','func','policy']
此时我们在于glance同级的文件中执行from glance.api import *就导入__all__中的内容(versions仍然不能导入)。
练习:
#执行文件中的使用效果如下,请处理好包的导入 from glance import * get() create_resource('a.conf') main() register_models('mysql')
#在glance.__init__.py中 from .api.policy import get from .api.versions import create_resource from .cmd.manage import main from .db.models import register_models __all__=['get','create_resource','main','register_models']
5、绝对导入和相对导入
我们的最顶级包glance是写给别人用的,然后在glance包内部也会有彼此之间互相导入的需求,这时候就有绝对导入和相对导入两种方式:
绝对导入:以glance作为起始
相对导入:用.或者..的方式最为起始(只能在一个包中使用,不能用于不同目录内)
例如:我们在glance/api/version.py中想要导入glance/cmd/manage.py
1 在glance/api/version.py 2 3 #绝对导入 4 from glance.cmd import manage 5 manage.main() 6 7 #相对导入 8 from ..cmd import manage 9 manage.main()
测试结果:注意一定要在于glance同级的文件中测试
1 from glance.api import versions
6、包以及包所包含的模块都是用来被导入的,而不是被直接执行的。而环境变量都是以执行文件为准的
比如我们想在glance/api/versions.py中导入glance/api/policy.py,有的同学一抽这俩模块是在同一个目录下,十分开心的就去做了,它直接这么做
1 #在version.py中 2 3 import policy 4 policy.get()
没错,我们单独运行version.py是一点问题没有的,运行version.py的路径搜索就是从当前路径开始的,于是在导入policy时能在当前目录下找到
但是你想啊,你子包中的模块version.py极有可能是被一个glance包同一级别的其他文件导入,比如我们在于glance同级下的一个test.py文件中导入version.py,如下
1 from glance.api import versions 2 3 ''' 4 执行结果: 5 ImportError: No module named 'policy' 6 ''' 7 8 ''' 9 分析: 10 此时我们导入versions在versions.py中执行 11 import policy需要找从sys.path也就是从当前目录找policy.py, 12 这必然是找不到的 13 '''
7、 绝对导入与相对导入总结
绝对导入与相对导入 # 绝对导入: 以执行文件的sys.path为起始点开始导入,称之为绝对导入 # 优点: 执行文件与被导入的模块中都可以使用 # 缺点: 所有导入都是以sys.path为起始点,导入麻烦 # 相对导入: 参照当前所在文件的文件夹为起始开始查找,称之为相对导入 # 符号: .代表当前所在文件的文件加,..代表上一级文件夹,...代表上一级的上一级文件夹 # 优点: 导入更加简单 # 缺点: 只能在导入包中的模块时才能使用 #注意: 1. 相对导入只能用于包内部模块之间的相互导入,导入者与被导入者都必须存在于一个包内 2. attempted relative import beyond top-level package # 试图在顶级包之外使用相对导入是错误的,言外之意,必须在顶级包内使用相对导入,每增加一个.代表跳到上一级文件夹,而上一级不应该超出顶级包
十 软件开发规范
#===============>star.py import sys,os BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(BASE_DIR) from core import src if __name__ == '__main__': src.run() #===============>settings.py import os BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__))) DB_PATH=os.path.join(BASE_DIR,'db','db.json') LOG_PATH=os.path.join(BASE_DIR,'log','access.log') LOGIN_TIMEOUT=5 """ logging配置 """ # 定义三种日志输出格式 standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' '[%(levelname)s][%(message)s]' #其中name为getlogger指定的名字 simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s' id_simple_format = '[%(levelname)s][%(asctime)s] %(message)s' # log配置字典 LOGGING_DIC = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'standard': { 'format': standard_format }, 'simple': { 'format': simple_format }, }, 'filters': {}, 'handlers': { #打印到终端的日志 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', # 打印到屏幕 'formatter': 'simple' }, #打印到文件的日志,收集info及以上的日志 'default': { 'level': 'DEBUG', 'class': 'logging.handlers.RotatingFileHandler', # 保存到文件 'formatter': 'standard', 'filename': LOG_PATH, # 日志文件 'maxBytes': 1024*1024*5, # 日志大小 5M 'backupCount': 5, 'encoding': 'utf-8', # 日志文件的编码,再也不用担心中文log乱码了 }, }, 'loggers': { #logging.getLogger(__name__)拿到的logger配置 '': { 'handlers': ['default', 'console'], # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕 'level': 'DEBUG', 'propagate': True, # 向上(更高level的logger)传递 }, }, } #===============>src.py from conf import settings from lib import common import time logger=common.get_logger(__name__) current_user={'user':None,'login_time':None,'timeout':int(settings.LOGIN_TIMEOUT)} def auth(func): def wrapper(*args,**kwargs): if current_user['user']: interval=time.time()-current_user['login_time'] if interval < current_user['timeout']: return func(*args,**kwargs) name = input('name>>: ') password = input('password>>: ') db=common.conn_db() if db.get(name): if password == db.get(name).get('password'): logger.info('登录成功') current_user['user']=name current_user['login_time']=time.time() return func(*args,**kwargs) else: logger.error('用户名不存在') return wrapper @auth def buy(): print('buy...') @auth def run(): print(''' 1 购物 2 查看余额 3 转账 ''') while True: choice = input('>>: ').strip() if not choice:continue if choice == '1': buy() #===============>db.json {"egon": {"password": "123", "money": 3000}, "alex": {"password": "alex3714", "money": 30000}, "wsb": {"password": "3714", "money": 20000}} #===============>common.py from conf import settings import logging import logging.config import json def get_logger(name): logging.config.dictConfig(settings.LOGGING_DIC) # 导入上面定义的logging配置 logger = logging.getLogger(name) # 生成一个log实例 return logger def conn_db(): db_path=settings.DB_PATH dic=json.load(open(db_path,'r',encoding='utf-8')) return dic #===============>access.log [2017-10-21 19:08:20,285][MainThread:10900][task_id:core.src][src.py:19][INFO][登录成功] [2017-10-21 19:08:32,206][MainThread:10900][task_id:core.src][src.py:19][INFO][登录成功] [2017-10-21 19:08:37,166][MainThread:10900][task_id:core.src][src.py:24][ERROR][用户名不存在] [2017-10-21 19:08:39,535][MainThread:10900][task_id:core.src][src.py:24][ERROR][用户名不存在] [2017-10-21 19:08:40,797][MainThread:10900][task_id:core.src][src.py:24][ERROR][用户名不存在] [2017-10-21 19:08:47,093][MainThread:10900][task_id:core.src][src.py:24][ERROR][用户名不存在] [2017-10-21 19:09:01,997][MainThread:10900][task_id:core.src][src.py:19][INFO][登录成功] [2017-10-21 19:09:05,781][MainThread:10900][task_id:core.src][src.py:24][ERROR][用户名不存在] [2017-10-21 19:09:29,878][MainThread:8812][task_id:core.src][src.py:19][INFO][登录成功] [2017-10-21 19:09:54,117][MainThread:9884][task_id:core.src][src.py:19][INFO][登录成功]
#===============>star.py import sys,os BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.append(BASE_DIR) from core import src if __name__ == '__main__': src.run() #===============>settings.py import os BASE_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__))) DB_PATH=os.path.join(BASE_DIR,'db','db.json') LOG_PATH=os.path.join(BASE_DIR,'log','access.log') LOGIN_TIMEOUT=5 """ logging配置 """ # 定义三种日志输出格式 standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' '[%(levelname)s][%(message)s]' #其中name为getlogger指定的名字 simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s' id_simple_format = '[%(levelname)s][%(asctime)s] %(message)s' # log配置字典 LOGGING_DIC = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'standard': { 'format': standard_format }, 'simple': { 'format': simple_format }, }, 'filters': {}, 'handlers': { #打印到终端的日志 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', # 打印到屏幕 'formatter': 'simple' }, #打印到文件的日志,收集info及以上的日志 'default': { 'level': 'DEBUG', 'class': 'logging.handlers.RotatingFileHandler', # 保存到文件 'formatter': 'standard', 'filename': LOG_PATH, # 日志文件 'maxBytes': 1024*1024*5, # 日志大小 5M 'backupCount': 5, 'encoding': 'utf-8', # 日志文件的编码,再也不用担心中文log乱码了 }, }, 'loggers': { #logging.getLogger(__name__)拿到的logger配置 '': { 'handlers': ['default', 'console'], # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕 'level': 'DEBUG', 'propagate': True, # 向上(更高level的logger)传递 }, }, } #===============>src.py from conf import settings from lib import common import time logger=common.get_logger(__name__) current_user={'user':None,'login_time':None,'timeout':int(settings.LOGIN_TIMEOUT)} def auth(func): def wrapper(*args,**kwargs): if current_user['user']: interval=time.time()-current_user['login_time'] if interval < current_user['timeout']: return func(*args,**kwargs) name = input('name>>: ') password = input('password>>: ') db=common.conn_db() if db.get(name): if password == db.get(name).get('password'): logger.info('登录成功') current_user['user']=name current_user['login_time']=time.time() return func(*args,**kwargs) else: logger.error('用户名不存在') return wrapper @auth def buy(): print('buy...') @auth def run(): print(''' 1 购物 2 查看余额 3 转账 ''') while True: choice = input('>>: ').strip() if not choice:continue if choice == '1': buy() #===============>db.json {"egon": {"password": "123", "money": 3000}, "alex": {"password": "alex3714", "money": 30000}, "wsb": {"password": "3714", "money": 20000}} #===============>common.py from conf import settings import logging import logging.config import json def get_logger(name): logging.config.dictConfig(settings.LOGGING_DIC) # 导入上面定义的logging配置 logger = logging.getLogger(name) # 生成一个log实例 return logger def conn_db(): db_path=settings.DB_PATH dic=json.load(open(db_path,'r',encoding='utf-8')) return dic #===============>access.log [2017-10-21 19:08:20,285][MainThread:10900][task_id:core.src][src.py:19][INFO][登录成功] [2017-10-21 19:08:32,206][MainThread:10900][task_id:core.src][src.py:19][INFO][登录成功] [2017-10-21 19:08:37,166][MainThread:10900][task_id:core.src][src.py:24][ERROR][用户名不存在] [2017-10-21 19:08:39,535][MainThread:10900][task_id:core.src][src.py:24][ERROR][用户名不存在] [2017-10-21 19:08:40,797][MainThread:10900][task_id:core.src][src.py:24][ERROR][用户名不存在] [2017-10-21 19:08:47,093][MainThread:10900][task_id:core.src][src.py:24][ERROR][用户名不存在] [2017-10-21 19:09:01,997][MainThread:10900][task_id:core.src][src.py:19][INFO][登录成功] [2017-10-21 19:09:05,781][MainThread:10900][task_id:core.src][src.py:24][ERROR][用户名不存在] [2017-10-21 19:09:29,878][MainThread:8812][task_id:core.src][src.py:19][INFO][登录成功] [2017-10-21 19:09:54,117][MainThread:9884][task_id:core.src][src.py:19][INFO][登录成功]