目录:
模块介绍
模块的使用:
- 2.1 import语句
- 2.2 from-import 语句
- 其他导入语法(as)
- 2.3 循环导入问题
- 2.4 搜索模块的路径与优先级
- 2.5 区分py文件的两种用途
- 2.6 编写一个规范的模块
包
3.1包的介绍
3.2包的使用
一、模块介绍
1.什么是模块?
模块是包含了一系列功能的集合体
2.模块的四种类别:
(1)、一个py文件就可以是一个模块,文件名为m1.py,模块名为m1
( 2)、一个包含有__int__.py文件的文件夹称之为包,也是一种模块
(3)、已被编译为共享库或DLL的C或C++扩展(了解)
( 4)、使用C编写并链接到python解释器的内置模块(了解)
3.模块的3种来源:
(1)、自带的:内置
标准库
( 2)、第三方模块:pip3 install requests
( 3)、自定义模块
4、为何要用模块:
拿来主义,提高开发效率
解决冗余问题,让程序变得更加清晰,方便管理
二、模块的使用
"""
import time
print(time)
2.1import语句
2.11. import首次导入模块发生了3件事:
(1)会触发模块对应文件的运行,产生一个模块的名称空间
(2)运行模块文件的代码,将运行过程中产生的名字都丢到模块的名称空间
(3)将模块名称空间的内存地址绑定当前空间中的名字spam
后续的导入不会触发文件的运行,直接引用之前的导入成果
例子: import spam
以spam.py为例来介绍模块的使用:文件名spam.py,模块名spam
#spam.py print('from the spam.py') money=1000 def read1(): print('spam模块:',money) def read2(): print('spam模块') read1() def change(): global money money=0
import的使用
#模块可以包含可执行的语句和函数的定义,这些语句的目的是初始化模块,它们只在模块名第一次遇到导入import语句时才执行(import语句是可以在程序中的任意位置使用的,且针对同一个模块很import多次,为了防止你重复导入,python的优化手段是:第一次导入后就将模块名加载到内存了,后续的import语句仅是对已经加载到内存中的模块对象增加了一次引用,不会重新执行模块内的语句),如下 #test.py import spam #只在第一次导入时才执行spam.py内代码,此处的显式效果是只打印一次'from the spam.py',当然其他的顶级代码也都被执行了,只不过没有显示效果. import spam import spam import spam ''' 执行结果: from the spam.py '''
# 使用 # print(spam.money) # print(spam.read1) # print(spam.read2) # print(spam.change) # 例1 # money=200 # spam.read1() # 例2 # def read1(): # print('run1.read1') # # spam.read2() # 例3 # money = 200 # spam.change() # # print(money) # print(spam.money)
2.12.import优缺点:
优点:访问的时候需要加前缀,指名道姓地问某一个名称空间要名字,肯定不会与当前名称空间中的名字冲突
# import spam
# money = 100000
# print(spam.money)
# print(money)
缺点:每次引用都需要加前缀,增加了代码的复杂度
2.13.其他语法:
# import spam, os, time # import spam as sm,os as xxx # print(sm.money)
2.2. 使用模块之from ... import...
2.21from导入模块也发生了三件事
(1)、会触发模块对应文件的运行,产生一个模块的名称空间
(2)、运行模块文件的代码,将运行过程中产生的名字都丢到模块的名称空间
(3)、在当前执行文件的名称空间中拿到一个名字,该名字指向指向模块名称空间中对应的名字
2.22.from...import...的使用
# from spam import money
# from spam import read1
# from spam import read2
# from spam import change
# 第二次导入直接引用之前的成果
# 使用 # money = 200 # print(money) # print(read1) # print(read2) # print(change) # money=200 # read1() # money = 200 # def read1(): # print('run2.read1',money) # # read1()
2.23 其他语法
from spam import money, read1, read2, change from spam import money as m, read1, read2 as r2, change
from spam import *
__all__=['money','read1'] #可以使用__all__来控制*(用来发布新版本),在spam.py中新增一行
# print(money)
# print(read1)
print(read2)
# print(change)
2.24 from导入优缺点:
优点:无需加前缀,代码简洁
缺点:容易与当前名称空间中的名字冲突
2.25 from...import 与import的对比
#唯一的区别就是:使用from...import...则是将spam中的名字直接导入到当前的名称空间中,所以在当前名称空间中,直接使用名字就可以了、无需加前缀:spam. #from...import...的方式有好处也有坏处 好处:使用起来方便了 坏处:容易与当前执行文件中的名字冲突
2.3 循环导入问题
m1.py
print('正在导入m1') from m2 import y x='m1
m2.py
print('正在导入m2') from m1 import x y='m2'
run.py
import m1
存在的问题
测试一:执行run.py会抛出异常
测试二:执行文件不等于导入文件,比如执行m1.py不等于导入了m1
解决方案
# 方案一:导入语句放到最后,保证在导入时,所有名字都已经加载过 # 文件:m1.py print('正在导入m1') x='m1' from m2 import y # 文件:m2.py print('正在导入m2') y='m2' from m1 import x # 文件:run.py内容如下,执行该文件,可以正常使用 import m1 print(m1.x) print(m1.y) # 方案二:导入语句放到函数中,只有在调用函数时才会执行其内部代码 # 文件:m1.py print('正在导入m1') def f1(): from m2 import y print(x,y) x = 'm1' # 文件:m2.py print('正在导入m2') def f2(): from m1 import x print(x,y) y = 'm2' # 文件:run.py内容如下,执行该文件,可以正常使用 import m1
m1.f1()
2.4搜索模块的路径与优先级
# 1、内存 # 2、内置模块 # 3、sys.path
# 例1 # import m3 # import time # # m3.f3() # time.sleep(10) # # import m3 # m3.f3() # 例2 # import time # # 例3: # import sys # sys.path.append(r'D:\全栈18期\day16\代码\aaa') # sys.path.append(r'D:\全栈18期\day16\代码\bbb') # sys.path.append(r'D:\全栈18期\day16\代码\ccc') # # import m4 # import m5 # import m6 # # m4.f4() # m5.f5() # m6.f6() # 例4: import sys # sys.path.append(r'D:\全栈18期\day16\代码') # from aaa import m4 # from bbb import m5 # from ccc import m6 # # m4.f4() # m5.f5() # m6.f6() # 例5: # 重要结论:如果被导入的模块是在执行程序所在的文件夹下,那么无需处理环境变量,以执行程序所在的文件夹为起始点进行查找即可 # 强调: # 1、导入语句中的点代表的是路径分割符 # 2、使用语句中的店代表的是问某一个名称空间要名字 # from ddd.eee.fff import m7 # m7.f7() # 例6: import sys sys.path.append(r'D:\全栈18期\day03') import m8 m8.f8()
2.5.区分py文件的2种用途
def f1():
print('f1')
def f2():
print('f2')
#python为我们内置了全局变量__name__, 当文件被当做脚本执行时:__name__ 等于'__main__' 当文件被当做模块导入时:__name__等于模块名 #作用:用来控制.py文件在不同的应用场景下执行不同的逻辑 if __name__ == '__main__':
#测试功能
f1()
f2()
三、软件开发的目录规范
我们在编写py文件时,需要时刻提醒自己,该文件既是给自己用的,也有可能会被其他人使用,因而代码的可读性与易维护性显得十分重要,为此我们在编写一个模块时最好按照统一的规范去编写,如下
#===============>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][登录成功]
四、包的使用
1首次导入包(如import pool)同样会做三件事:
1、执行包下的__init__.py文件
2、产生一个新的名称空间用于存放__init__.py执行过程中产生的名字
3、在当前执行文件所在的名称空间中得到一个名字pool,该名字指向__init__.py的名称空间
2.绝对导入:以执行文件的sys.path为起始点开始导入,称之为绝对导入
优点:执行文件与被导入的模块中都可以使用
缺点:所有导入都是以sys.path为起始点,导入麻烦
相对导入:参照当前所在文件的文件夹为起始开始查找,称之为相对导入
符号: .代表当前所在文件的文件加,..代表上一级文件夹,...代表上一级的上一级文件夹
优点:导入更加简单
缺点:只能在导入包中的模块时才能使用
注意:1. 相对导入只能用于包内部模块之间的相互导入,导入者与被导入者都必须存在于一个包内
2. attempted relative import beyond top-level package # 试图在顶级包之外使用相对导入是错误的,言外之意,必须在顶级包内使用相对导入,每增加一个.代表跳到上一级文件夹,而上一级不应该超出顶级包
包以及包所包含的模块都是用来被导入的,而不是被直接执行的。而环境变量都是以执行文件为准的
3.# 导包就是在导包下的__init__.py
# xxx.名字,都来自于__init__.py
import xxx
print(xxx.x)
print(xxx.y)
4.总结包的使用需要牢记三点
1、导包就是在导包下__init__.py文件
2、包内部的导入应该使用相对导入,相对导入也只能在包内部使用,而且...取上一级不能出包
3、使用语句中的点代表的是访问属性
m.n.x ----> 向m要n,向n要x
而导入语句中的点代表的是路径分隔符
import a.b.c --> a/b/c,文件夹下a下有子文件夹b,文件夹b下有子文件或文件夹c
所以导入语句中点的左边必须是一个包