15.01 模块与包

目录:

     模块介绍

     模块的使用:

  • 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
所以导入语句中点的左边必须是一个包

 

 

 
 
 

 

posted @ 2021-08-13 17:48  甜甜de微笑  阅读(40)  评论(0编辑  收藏  举报