模块

一个模块就是包含了python定义和声明的文件,文件名就是模块名字加上.py后缀,通过导入模块来引入其他文件的功能。

import加载的模块分为四个通用类型:

使用python编写的代码(.py文件)

已被编译为共享库或DLL的C或C++扩展

3.包好一组模块的包

4.使用C编写并链接到python解释器的内置模块

导入模块会触发的动作:

执行源文件,直接运行

以源文件为基础,产生源文件的名称空间

在当前位置拿到一个模块名,指向模块中创建的名称空间

导入和引入模块中的变量:

import file_op # 导入模块,模块可以是一个file_op.py的文件

print(file_op.a) # 使用模块名加 . 调用

file_op.findnum(1,file_op.a)

如果要导入多个模块:

import os,sys,spam

在引用变量时,如果要直接导入函数和模块变量,而不是用模块名.xxx的方式调用,可以使用from关键字:

from file_op import a,findnum

print(a)

使用这种方式有如下优缺点:

优点:使用源文件的名字时,无需加前缀

缺点: 容易与当前文件的名称空间内的名字混淆,如果本地定义了相同的变量,调用时则会先调用本地变量。

模块的别名

对导入的模块可以使用别名:

import file_op as f

print(f.a)

对于导入的变量和函数也可以使用别名:

from file_op import a as a_list

别名的使用场景:

当模块名称比较复杂,或者比较长不方便书写的时候,可以使用简单的别名进行替换。

当使用不同包中相同方法和函数变量时(如,为不同版本,或者不同品牌编写的相同功能模块),可以使用同一个别名,来进行调用,减少重复的代码。

如程序根据用户选择的数据库类型来进行不同的操作:

sql_type=input('sql_type:')
if sql_type == 'mysql':
import mysql as sql
elif sql_type == 'oracle':
import oracle as sql
sql.sqlpare()

导入模块*的使用

如果模块中的变量很多,需要在本地进行大量引用时,使用如下方式,可以一次导入全部的变量和函数:

from module1 import *

但是这种方式并不是最佳的方式,一次导入所有的变量和函数,有可能与本地的命名产生冲突。

可以在模块中使用特殊的命名方式,来防止使用*的方式导入:

_a=[1,2,3]

使用from module1 import *的方式将不会导入模块中以_开头的变量和函数,从而禁止了对此模块使用import *的表示方法。

模块中,指定需要导入的变量:

__all__=['a','b']

使用这种方式可以from module1 import * 导入这些指定的变量.

模块搜索路径

模块只在第一次导入时才会执行,之后的导入都是直接引用内存已经存在的结果.

使用'modules1' in sys.modules可以查看内存中是否已经加载了modules1模块:

import sys

import file_op

print('file_op' in sys.modules)

返回为 True

python 导入模块加载的顺序:

1、在使用import导入时,先在内存中查找是否有此模块。

2、如果没有,优先寻找内置模块。

3、如果没有,再从sys.path中的找, sys.path从以下位置初始化:

- 先搜索执行文件所在的当前目录

- PYTHONPATH(包含一系列目录名,与shell变量PATH语法一样)

- 依赖安装时默认指定的(系统环境变量设置的目录等)

提示:自定义的模块不能与内置的模块重名,否则自定义的模块将不会生效。

使用sys.path添加路径:

sys.path.append(r'D:\python36-project\test\file')

python文件的两种用途

python文件有两种使用方式:

当作脚本直接运行

当作模块导入

当我们在编写代码时,又是需要将某些脚本文件当作模块导入,但是不想让其直接执行,或者在编写模块时,需要对模块中的函数进行测试,需要让模块以脚本方式运行,这样就存在了冲突。

为了解决这个问题,可以使用if __name__ == '__main__':的方式,使文件既可以当作脚本运行,也可以当作模块导入:

def func1():
print('func1')
def func2():
print('func2')
def func3():
print('func3')
if __name__ == '__main__':
func1()
func2()
func3()

包的导入

包是一种通过使用‘.模块名’来组织python模块名称空间的方式。一个包含有init.py文件的文件夹,所以其实我们创建包的目的就是为了用文件夹将文件/模块组织起来

需要强调的是:

1. 在python3中,即使包下没有__init__.py文件,import 包仍然不会报错,而在python2中,包下一定要有该文件,否则import 包报错.

2. 创建包的目的不是为了运行,而是被导入使用,记住,包只是模块的一种形式而已,包的本质就是一种模块

使用包的注意事项:

包相关的导入语句也分为import和from ... import ...两种,但是无论哪种,无论在什么位置,在导入时都必须遵循一个原则:凡是在导入时带点的,点的左边都必须是一个包,否则非法。可以带有一连串的点,如item.subitem.subsubitem,但都必须遵循这个原则。但对于导入后,在使用时就没有这种限制了,点的左边可以是包,模块,函数,类(它们都可以用点的方式调用自己的属性)。

import导入文件时,产生名称空间中的名字来源于文件,import 包,产生的名称空间的名字同样来源于文件,即包下的__init__.py文件,导入包本质就是在导入该文件。

在当前文件a中导入包时,sys.path是以当前文件a的路径为准,在包中的__init__.py文件导入模块,也是以当前的文件a的路径为准

包的导入,需要在__init__.py文件中指定模块。

相对导入和绝对导入

在对包进行内部模块导入时,使用绝对导入:

从包的顶层文件夹下开始,依次调用路径,如:

from glance.cmd import manage

manage.main()

使用绝对导入,有一些潜在的问题,当最外层的包名发生改变的时候,包中所有文件的引用都要改变,这样会操作起来会比较麻烦。

使用相对路径导入,就可以避免这个问题:

from ..cmd import manage

软件开发规范

在开发软件的时候,使用包的目录结构。如下图:

image.png

在对包进行导入的时候可以使用如下方式:

# start.py 启动文件:
# coding=utf-8
import sys,os
current_file_path=(os.path.abspath(__file__)) # 当前执行文件的路径
BASE_DIR=(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) # 主目录路径
sys.path.append(BASE_DIR) # 添加主目录路径到syspath
# 调用主干中执行模块的功能:
from core import src
if __name__ == '__main__':
src.run()

在在core文件中调用其它目录中的模块。可以直接使用如下方式导入模块:

# settings 文件

from conf import settings # 以顶层目录为起始

def run():

print('run')

print(settings.x)

其他文件模块的导入类似,都是以顶层目录为其实目录。

日志模块logging

python 中有供我们使用的日志模块,对于不同的日志等级,设定了不同级别。

CRITICAL = 50 #FATAL = CRITICAL
ERROR = 40
WARNING = 30 #WARN = WARNING
INFO = 20
DEBUG = 10
NOTSET = 0 #不设置

默认级别为warning,默认打印到终端,指定日志级别后,会根据日志级别自下而上记录日志,如设定日志级别为ERROR那么只会打印ERROR和CRITICAL级别的日志。

导入日志模块:

import logging
logging.debug('调试debug')
logging.info('消息info')
logging.warning('警告warn')
logging.error('错误error')
logging.critical('严重critical')

默认只会从warning 及以上级别的日志执行打印。

如果要将日志写入文件,可在logging.basicConfig()函数中通过具体参数来更改logging模块默认行为,可用参数有:

filename:用指定的文件名创建FiledHandler,这样日志会被存储在指定的文件中。

filemode:文件打开方式,在指定了filename时使用这个参数,默认值为“a”还可指定为“w”。

format:指定handler使用的日志显示格式。

datefmt:指定日期时间格式。

level:设置rootlogger的日志级别 ,可以使用数字或名称去定义

stream:用指定的stream创建StreamHandler。可以指定输出到sys.stderr,sys.stdout或者文件,默认为sys.stderr。若同时列出了filename和stream两个参数,则stream参数会被忽略。

日志的格式还可以指定如下参数:

#format 格式
%(name)s:Logger的名字,并非用户名,详细查看
%(levelno)s:数字形式的日志级别
%(levelname)s:文本形式的日志级别
%(pathname)s:调用日志输出函数的模块的完整路径名,可能没有
%(filename)s:调用日志输出函数的模块的文件名
%(module)s:调用日志输出函数的模块名
%(funcName)s:调用日志输出函数的函数名
%(lineno)d:调用日志输出函数的语句所在的代码行
%(created)f:当前时间,用UNIX标准的表示时间的浮 点数表示
%(relativeCreated)d:输出日志信息时的,自Logger创建以 来的毫秒数
%(asctime)s:字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒
%(thread)d:线程ID。可能没有
%(threadName)s:线程名。可能没有
%(process)d:进程ID。可能没有
%(message)s:用户输出的消息
模块的使用:
import logging
# 打印到文件,并制定日志格式
logging.basicConfig(filename='access.log',
format='%(asctime)s - %(name)s - %(levelname)s -%(module)s: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S %p',
level=10) # 日志级别
logging.debug('调试debug')
logging.info('消息info')
logging.warning('警告warn')
logging.error('错误error')
logging.critical('严重critical')
在access.log文件中会记录如下信息:
2017-12-07 10:53:20 AM - root - DEBUG -file_op: 调试debug
2017-12-07 10:53:20 AM - root - INFO -file_op: 消息info
2017-12-07 10:53:20 AM - root - WARNING -file_op: 警告warn
2017-12-07 10:53:20 AM - root - ERROR -file_op: 错误error
2017-12-07 10:53:20 AM - root - CRITICAL -file_op: 严重critical

日志模块的对象和使用

logging模块有Formatter,Handler,Logger,Filter对象。

分别指具有以下功能:

logger:产生日志的对象

Filter:过滤日志的对象

Handler:接收日志然后控制打印到不同的地方,FileHandler用来打印到文件中,StreamHandler用来打印到终端

Formatter对象:可以定制不同的日志格式对象,然后绑定给不同的Handler对象使用,以此来控制不同的Handler的日志格式,示例:

'''
critical=50
error =40
warning =30
info = 20
debug =10
'''
import logging
#1、logger对象:负责产生日志,然后交给Filter过滤,然后交给不同的Handler输出
logger=logging.getLogger(__file__)
#2、Filter对象:不常用,略
#3、Handler对象:接收logger传来的日志,然后控制输出
h1=logging.FileHandler('t1.log') #打印到文件,指定不同文件
h2=logging.FileHandler('t2.log') #打印到文件
h3=logging.StreamHandler() #打印到终端
#4、Formatter对象:日志格式
formmater1=logging.Formatter('%(asctime)s - %(name)s - %(levelname)s -%(module)s: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S %p',)
formmater2=logging.Formatter('%(asctime)s : %(message)s',
datefmt='%Y-%m-%d %H:%M:%S %p',)
formmater3=logging.Formatter('%(name)s %(message)s',)
#5、为Handler对象绑定格式
h1.setFormatter(formmater1)
h2.setFormatter(formmater2)
h3.setFormatter(formmater3)
#6、将Handler添加给logger并设置日志级别
logger.addHandler(h1)
logger.addHandler(h2)
logger.addHandler(h3)
logger.setLevel(10)
#7、测试
logger.debug('debug')
logger.info('info')
logger.warning('warning')
logger.error('error')
logger.critical('critical')

提示:logger是第一级过滤,然后才能到handler,我们可以给logger和handler同时设置level

Logger也是根据日志级别首先对消息进行过滤 - 如果将logger设置为INFO,并且所有的handler设置为DEBUG,那么在handler上仍然不会收到DEBUG消息 - 它们将被logger本身拒绝。 如果将记录器设置为DEBUG,但所有handler都设置为INFO,则不会收到任何DEBUG消息 - 因为当logger说“ok,允许此操作”时,handlers却会拒绝它(DEBUG < INFO)

日志的应用

"""

logging配置

"""

import os

import logging.config

# 定义三种日志输出格式 开始

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'
# 定义日志输出格式 结束
logfile_dir = os.path.dirname(os.path.abspath(__file__)) # log文件的目录
logfile_name = 'all2.log' # log文件名
# 如果不存在定义的日志目录就创建一个
if not os.path.isdir(logfile_dir):
os.mkdir(logfile_dir)
# log文件的全路径
logfile_path = os.path.join(logfile_dir, logfile_name)
# 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': logfile_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)传递
},
},
}
def load_my_logging_cfg():
logging.config.dictConfig(LOGGING_DIC) # 导入上面定义的logging配置
logger = logging.getLogger(__name__) # 生成一个log实例
logger.info('It works!') # 记录该文件的运行状态
if __name__ == '__main__':
load_my_logging_cfg()
使用:
"""
MyLogging Test
"""
import time
import logging
import my_logging # 导入自定义的logging配置
logger = logging.getLogger(__name__) # 生成logger实例
def demo():
logger.debug("start range... time:{}".format(time.time()))
logger.info("中文测试开始。。。")
for i in range(10):
logger.debug("i:{}".format(i))
time.sleep(0.2)
else:
logger.debug("over range... time:{}".format(time.time()))
logger.info("中文测试结束。。。")
if __name__ == "__main__":
my_logging.load_my_logging_cfg() # 在你程序文件的入口加载自定义logging配置
demo()

注意:

有了上述方式我们的好处是:所有与logging模块有关的配置都写到字典中就可以了,更加清晰,方便管理

我们需要解决的问题是:

1. 从字典加载配置:logging.config.dictConfig(settings.LOGGING_DIC)

2. 拿到logger对象来产生日志,logger对象都是配置到字典的loggers 键对应的子字典中的,按照我们对logging模块的理解,要想获取某个东西都是通过名字,也就是key来获取的

于是我们要获取不同的logger对象就是

logger=logging.getLogger('loggers子字典的key名')

但问题是:如果我们想要不同logger名的logger对象都共用一段配置,那么肯定不能在loggers子字典中定义n个key

'loggers': {
'l1': {
'handlers': ['default', 'console'], #
'level': 'DEBUG',
'propagate': True, # 向上(更高level的logger)传递
},
'l2: {
'handlers': ['default', 'console' ],
'level': 'DEBUG',
'propagate': False, # 向上(更高level的logger)传递
},
'l3': {
'handlers': ['default', 'console'], #
'level': 'DEBUG',
'propagate': True, # 向上(更高level的logger)传递
},
}
我们的解决方式是,定义一个空的key
'loggers': {
'': {
'handlers': ['default', 'console'],
'level': 'DEBUG',
'propagate': True,
},
}

这样我们再取logger对象时

logging.getLogger(name),不同的文件name不同,这保证了打印日志时标识信息不同,但是拿着该名字去loggers里找key名时却发现找不到,于是默认使用key=''的配置