Python模块和包的导入

模块

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

  1. 使用python编写的代码(.py文件)
  2. 已被编译为共享库或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

别名的使用场景:

  1. 当模块名称比较复杂,或者比较长不方便书写的时候,可以使用简单的别名进行替换。
  2. 当使用不同包中相同方法和函数变量时(如,为不同版本,或者不同品牌编写的相同功能模块),可以使用同一个别名,来进行调用,减少重复的代码。

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

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文件有两种使用方式:

  1. 当作脚本直接运行
  2. 当作模块导入
    当我们在编写代码时,又是需要将某些脚本文件当作模块导入,但是不想让其直接执行,或者在编写模块时,需要对模块中的函数进行测试,需要让模块以脚本方式运行,这样就存在了冲突。
    为了解决这个问题,可以使用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. 创建包的目的不是为了运行,而是被导入使用,记住,包只是模块的一种形式而已,包的本质就是一种模块

使用包的注意事项:

  1. 包相关的导入语句也分为import和from ... import ...两种,但是无论哪种,无论在什么位置,在导入时都必须遵循一个原则:凡是在导入时带点的,点的左边都必须是一个包,否则非法。可以带有一连串的点,如item.subitem.subsubitem,但都必须遵循这个原则。但对于导入后,在使用时就没有这种限制了,点的左边可以是包,模块,函数,类(它们都可以用点的方式调用自己的属性)。
  2. import导入文件时,产生名称空间中的名字来源于文件,import 包,产生的名称空间的名字同样来源于文件,即包下的__init__.py文件,导入包本质就是在导入该文件。
  3. 在当前文件a中导入包时,sys.path是以当前文件a的路径为准,在包中的__init__.py文件导入模块,也是以当前的文件a的路径为准
  4. 包的导入,需要在__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=''的配置

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,637评论 18 139
  • Day05的课程要点记录详细教程地址:Day5 - 常用模块学习 | 第四篇:模块 一、模块介绍 1.1 定义 模...
    乘风逐月阅读 445评论 0 1
  • 本文章是我大概三年前,在上家单位使用 Python 工作时结合官方文档做的整理。现在 Python 官方文档听说已...
    好吃的野菜阅读 216,622评论 14 232
  • 位运算 按位与运算:&双目运算符,二进制各位都为1结果位才为1,否则为0。通常参与位运算某些位清零或保留某些位 按...
    酸菜牛肉阅读 223评论 2 2
  • 然后是埋点辅助类: 接下来就是在具体的类中实现了:
    AKyS佐毅阅读 7,177评论 7 59