模块和包

一、模块

  1、什么是模块?
    常见的场景: 一个模块就是一个包含了python定义和声明的文件,文件名就是模块名字加上.py的后缀。但其实import加载的模块分为四个通用类别: 
      1 使用python编写的代码(.py文件)
      2 已被编译为共享库或DLL的C或C++扩展
      3 包好一组模块的包
      4 使用C编写并链接到python解释器的内置模块
  2、为什么要使用模块?
    如果你退出python解释器然后重新进入,那么你之前定义的函数或者变量都将丢失,因此我们通常将程序写到文件中以便永久保存下来,需要时就通过python test.py方式去执行,此时test.py被称为脚本script。
    随着程序的发展,功能越来越多,为了方便管理,我们通常将程序分成一个个的文件,这样做程序的结构更清晰,方便管理。这时我们不仅仅可以把这些文件当做脚本去执行,还可以把他们当做模块来导入到其他的模块中,实现了功能的重复利用。
  3、如何自己写一个模块?

          创建一个py文件,给它起一个符合变量名命名规则的名字,这个名字就是模块名。如下示例:

    # my_module.py
    print(12345)

    name = 'alex'

    def read1():
        print('hello world')

    def read2():
        print('in read1 func',name)

    print(54321)
    注:下文提到的my_module模块就是此示例内容。
my_module.py

二、导入模块

  1、import导入模块

    正如我们之前所见,可以通过import导入模块,比如import my_module

       2、怎么使用my_module模块中的名字?

              print(my_module.name)

              my_module.read1()

       3、import的命名空间

              模块和当前文件在不同的命名空间中。

  4、在导入模块的过程中发生了什么?

    导入一个模块就是执行一个模块。

    模块导入的过程如下:

    # 找到这个模块
    # 判断这个模块是否被导入过了
    # 如果没有被导入过
          # 创建一个属于这个模块的命名空间
          # 让模块的名字 指向 这个空间
          # 执行这个模块中的代码

  5、模块是否可以被重复导入?怎么判断一个模块已经被导入过了?

              import sys

              print(sys.module)

              模块的导入相当于执行这个模块所在的文件,模块的多次导入不会重复执行。

  6、给模块起别名(起了别名后,使用这个模块就都使用别名引用变量了)

              import my_module as m

              m.read1()   # 使用别名引用,而不能用my_module.read1()了

              给模块起别名示例(当需要导入不同模块,且导入不同模块所做的操作相同,可以给模块起别名):

      def func(dic, t='json'):
          if t == 'json':
              import json as aaa
          elif t == 'pickle':
              import pickle as aaa
          return aaa.dumps(dic)
            

  7、导入多个模块

              多行导入:

              import os

              import time

              一行导入:

              import os,time   或者   import os as o,time as t

              规范建议:模块应该一个一个的导入,顺序为:内置模块、扩展(第三方)模块、自定义模块,如下格式:

    import os

    import django

    import my_module

  8、如何使用from import

              from my_module import read2

              read2()

              结论:import谁就只能用谁。

  9、当前文件命名空间和模块的命名空间的问题,有如下代码,分析结果:

   from my_module import read1   # 此时执行my_module模块
    def read1():
        print('in my read1')
    read1()

    from my_module import read2   # 此时不再执行my_module模块
    read2()
    # 结果为:
    # 12345
    # 54321
    # in my read1
    # in read1 func alex
   分析:from import导入的过程:
        # 1.找到要被导入的模块
        # 2.判断这个模块是否被导入过
        # 3.如果这个模块没被导入过
            # 创建一个属于这个模块的命名空间
            # 执行这个文件
            # 找到你要导入的变量
            # 给你要导入的变量创建一个引用,指向要导入的变量        

  10、导入多个名字、给导入的名字起别名

    from my_module import read1 as r1,read2 as r2
    def read1():
        print('in my read1')
    r1() # 模块中的read1
    r2() # 模块中的read2
    read1() # 本文件中的read1

  11、from my_module import *(代表导入这个模块中的所有名字),使用如下:

    name = 'egon'
    print(name)   # 结果为:egon,模块中的name被本文件的 name='egon'所覆盖
    read1  # 模块中的read1
    read2  # 模块中的read2

    注意:*和__all__的关系:*会参考__all__中的变量,如果没有__all__变量,则导入所有名字,当有__all__变量时,则__all__能够约束*导入的变量的内容,且*里边变量只能是字符串,如下示例:

                     __all__ = [‘name’]

三、模块引用中的情况

       1、模块的循环引用(***)

              模块之间不能形成循环引用,否则就会报错。

       2、模块的加载与修改(*)

              已经被导入的模块发生了修改,是不会被正在运行的程序感知到的,可以用importlib.reload(my_module)重新加载这个模块被感知,但是一般不用,知道即可;

              所以要想修改的模块被正在运行中的程序感知到,可以重启这个程序,而不是重新加载这个模块;

       3、把模块当成脚本执行(*****)

              首先要知道,同一个py文件,直接运行这个文件,这个文件就是一个脚本,而导入这个文件,这个文件就是一个模块。

              其次,当一个py文件被当做脚本执行时,它的__name__ ='__main__';当一个模块被调用时,它的__name__ ='模块的名字'

              最后,当一个py文件被当做一个脚本的时候,能够独立地提供一个功能,能自主完成交互;当成一个模块的时候,能够被导入者调用这个功能,不能自主交互。

              所以,可以按照如下这样写:

    # if __name__ == '__main__':
          # 代码
    # 写在这里面的代码只有这个文件被当做脚本执行的时候才执行

  4、模块的搜索路径(*****)

              被当做脚本执行的文件 的 同目录下的模块,可以被直接导入,除此之外其他路径下的模块,在被导入的时候需要自己修改sys.path列表。

四、包

       什么是包:文件夹中有一个__init__.py文件,是几个模块的集合。如下示例是一个包的目录结构:

    # glance/
    # ├── __init__.py
    # ├── api
    # │   ├── __init__.py
    # │   ├── policy.py
    # │   └── versions.py
    # ├── cmd
    # │   ├── __init__.py
    # │   └── manage.py
    # └── db
    #     ├── __init__.py
    #     └── models.py

  1、从包中导入模块(精确到模块,不需要设计__init__.py文件)(必须掌握,很简单)

    # 1.1  import方式
    import glance.api.policy   # 导入模块
    glance.api.policy.get()   # 使用模块中的方法

    import glance.api.policy as policy  # 导入模块,并给模块起别名
    policy.get()  # 使用模块中的方法

    # 1.2  form import方式
    from glance.api import policy
    policy.get()

    from glance.api.policy import get
    get()

  总结:1)从包中引入模块的上述两种方式,无论哪种,无论在什么位置,在导入时都必须遵循一个原则:凡是在导入时带点的,点的左边都必须是一个包,否则非法。可以带一连串的点,但都必须遵循这个原则。

       2)from import方式中,import后边必须不能带点,否则有语法错误。

2、导入包

              直接导入包:import glance

              但是导入一个包并不意味包下面的所有内容都是可以被使用的,即直接导入包什么都干不了,那么导入一个包到底发生了什么?导入一个包相当于执行了这个包下面的__init__.py文件

              所以直接导入包时,我们要通过设计__init__.py文件,来完成导入包之后的操作,这时又分为下面两种方式;绝对导入和相对导入:

  2.1  绝对导入

    # glance/
    # ├── __init__.py    (from glance import api)from glance import cmd)
                (from glance import db)
    # ├── api
    # │   ├── __init__.py   (from glance.api import policy)from glance.api import version)
    # │   ├── policy.py
    # │   └── versions.py
    # ├── cmd
    # │   ├── __init__.py   (from glance.cmd import manage)
    # │   └── manage.py
    # └── db
    #     ├── __init__.py   (from glance.db import models)
    #     └── models.py
绝对导入

  绝对导入的缺点:1)所有的导入都要从一个根目录下往后解释文件夹之间的关系;

          2)如果当前导入包的文件和被导入的包的位置关系发生了变化,那么所有的init文件都要做相应的调整;

  2.2  相对导入

    # glance/
    # ├── __init__.py    (from . import api)from . import cmd)
            (from . import db)
    # ├── api
    # │   ├── __init__.py   (from . import policy)from . import version)
    # │   ├── policy.py
    # │   └── versions.py
    # ├── cmd
    # │   ├── __init__.py   (from . import manage)
    # │   └── manage.py     (from ..api import policy)
    # └── db
    #     ├── __init__.py   (from . import models)
    #     └── models.py
相对导入

  相对导入的优点:1)不需要反复去修改路径,只要一个包中的所有文件夹和文件的相对位置不发生改变;

          2)也不需要关心当前这个包和被执行的文件之间的层级关系;

  注意:含有相对导入的py文件不能被直接执行,且必须放在包中被导入的调用才能正常的使用。所以,在执行一个py脚本的时候,这个脚本以及和这个脚本同级的模块中只能用绝对导入。

  我们再看下面一个现象:

    import urllib
    urllib.request()  # 报错:AttributeError: module 'urllib' has no     attribute 'request'

    # from urllib import request
    # print(request)  # <module 'urllib.request' from     'C:\Python36\lib\urllib\request.py'>
    分析:urllib是一个包,单纯的导入一个包啥也不会发生,包中的request.py也不能用。

  总结:如果只是从包中导入模块的话,那么我们不需要做任何多余的操作,直接导入就行了。如果我们希望导入包的时候,能够顺便把模块也导入进来,就需要设计_init__文件,这时有上述绝对目录的设计和相对目录的设计,而且两种方式各有千秋。

五、项目开发规范

  # myProject/
  # ├── __init__.py    
  # ├── bin   (当前项目的启动脚本在这里)
  # │   ├── __init__.py   
  # │   ├── start.py
  # ├── core   (核心代码都在这里)
  # │   ├── __init__.py   
  # │   └── main.py
  # └── db
  #     ├── __init__.py   
  #     └── userInfo.json
  # └── lib(不是内置和第三方模块,可能是自己写的,较完善且跟此程序相关性小)
  #     ├── __init__.py   
  #     └── models.py
  # └── conf(配置文件,多处用到某个值以后可能会被修改,则写到配置文件中)
  #     ├── __init__.py   
  #     └── config.py
  # └── log(日志文件,供用户查看追责或者公司分析数据)
  #     ├── __init__.py   
  #     └── all.log
原文地址:https://www.cnblogs.com/li-li/p/9526636.html