模块基础

模块基础

模块的四种形式

一、什么是模块?

模块是一系列功能的集合体,而函数是某一个功能的集合体,因此模块可以看成是一堆函数的集合体。一个py文件内部可以放一堆函数,因此一个py文件就可以看成一个模块。如果这个py文件的文件名为module.py,模块名则是module

二、模块的四种形式

模块的三种来源:内置的(python解释器自带),第三方的(别人写的),自定义的(你自己写的)

在python中,总共有以下4中形式的模块:

​ 1.自定义模块:如果你自己写一个py文件,在文件内写入一推函数,则它被称为自定义模块,即使用python编写的.py文件

​ 2.第三方模块:已被编译为共享库或DLL的C或C++扩展

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

​ 4.包:把一系列模块组织到一起的文件夹(注:文件夹下有一个__init__.py文件,该文件夹称之为包)

三、为什么要用模块?

​ 1.用第三方或者内置的模块是一种拿来主义,可以极大地提升开发效率。

​ 2.自定义模块,将我们自己程序中用到的公共功能,写入一个python文件,然后程序的各部分组件可以通过导入的方式来应用自定义模块的功能。

使用自己写的模块(自定义的):当程序比较庞大的时候,你的项目不可能只在一个py中 那么当多个文件中都需要使用相同的方法的时候 可以将该公共的方法写到一个py文件中 其他的文件以模块的形式导过去直接调用即可

四、如何用模块?

一般我们使用import和from...import...导入模块。

import和from...import...

一般使用import和from...import...导入模块。

一、import模块名

import首次导入模块发生了3件事:运行执行文件,首先会创建一个执行文件的名称空间。

首次导入模块(***************)

​ 1.以模板为准创造一个模块的名称空间

​ 2.执行模块对应的文件,将执行过程中产生的名字都存放到模块的名称空间

​ 3.在当前执行文件中拿到一个模块名

模块的重复导入会直接引用之前创造好的结果,不会重复执行模块的文件,即重复导入会发生:md = md = 模块名称空间的内存地址。

使用import导入模块,访问模块名称空间的名字统一句式:模块名.名字

​ 1.指名道姓的访问模块中的名字,永远不会与执行文件中的名字冲突。

​ 2.你如果想访问模块中的名字,必须用模块名.名字的方式。

# 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
# run.py
import spam
import spam
import spam
import spam  # from the spam.py

只要能拿到函数名,无论在哪都可以通过函数加括号来调用这个函数(会回到函数定义阶段,依次执行代码)

函数在定义阶段,名字的查找顺序就已经固定死了,不会因为调用位置的变化而变化。

只要当几个模块有相同部分或者属于用一个模块,可以使用上面的方法,当几个模块没有联系的情况下,应该分多次导入。

import os
import time
import md
ps:通常导入模块的句式会写在文件的开头
当模块名字比较复杂的情况下,可以给该模块名取别名
# from spam as sm

异常捕捉

d = (i for i in range(3))
while True:
    try:
        print(d.__next__())
        print(name)
        sdgfghg
    except Exception:  # 适用于一切错误
        break

异常捕捉有两大类:

​ 1.语法结构错误:需要你当场修改,异常捕获没法完成

​ 2.逻辑错误:异常捕获可以处理

二、from 模块名import具体的功能

from...import...首次导入模块发生的3件事:首次先创建执行文件的名称空间

​ 1.以模块为准创造一个模块的名称空间

​ 2.执行模块对应的文件,将执行过程中产生的名字都丢到模块的名称空间

​ 3.在当前执行文件的名称空间直接拿到模块名称空间中某个值的名字

利用from...import...句式的特点:

​ 1.访问模块中的名字不需要加模块名前缀

​ 2.在访问模块中的名字可能会与当前执行文件中的名字冲突。

from md1 import money,read1,read2,change
print(money)
def read1():
    print('run.py')
read1()    
# 补充
from md1 import *  # 一次性将md1模块中的名字全部加载过来,不推荐使用,并且你根本不知道到底有哪些名字可以使用
print(money)
print(read1)

__all__可以指定当所在py文件被当做模块导入的时候,可以限制导入者能够拿到的名字个数。

# spam.py 
__all__ = ['money','read1']  # 只允许导入'money'和'read1'
# run.py
from spam import *  # 导入spam.py文件内的所有功能,但会受限于__all__

三、import和from...import...的异同

相同点:

​ 1.两者都会执行模块对应的文件,两者都会产生模块的名称空间

​ 2.两者调用时,需要跑到定义时寻找作用域关系,与调用位置无关。

不同点:

​ 1.import需要加前缀;from...import...不需要加前缀

循环导入问题

一、什么是循环导入?

# m1.py
print('from m1.py')
from m2 import x

y = 'm1'

1.创建m2的名称空间

2.执行m2.py,将执行产生的名字都丢到m2.py

3.在当前执行文件中拿到m2.x

# m2.py
print('from m2.py')
from m1 import y

x = 'm2'

1.创建m1的名称空间

2.执行m1.py,将执行产生的名字丢到m1.py

3.在当前执行文件中拿到m1.py

# run.py
import m1

1.创建m1的名称空间

2.执行m1.py,将执行产生的名字丢到m1.py

3.在当前执行文件中拿到m1

  • 如果运行run.py,则会报错ImportError: cannot import name 'y'
  • 如果运行m1.py,则会报错ImportError: cannot import name 'y'
  • 如果运行m2.py,则会报错ImportError: cannot import name 'y'

循环导入问题

# m1.py
from n2 import y
x = 10

# m2.py
from m2 import x
y =10

为什么会有循环导入问题:

1.m1文件需要导入m2文件的y

2.m2文件需要导入m1文件的x

3.代码自上而下运行,m1需要m2的y,然后会去m2的名称空间找到y,但是找y前,需要运行m2的代码,m2的代码第一句是去找m1的x,然后又回去运行m1的代码,m1的第一行代码又是寻找m2的y

4.就是说m1只能运行第一行,m2也只能运行第一行,所以出现了循环导入问题

二、解决方案

我们可以使用函数定义阶段只识别语法不执行代码的特性解决循环导入的问题,我们也可以从本质上解决循环导入的问题,但是最好的解决方法是不要出现循环导入。

2.1 方案一

# m1.py
print('from m1.py')

def func():
    from m2 import x
    print(x)
y = 'm1'    
# m2.py
print('from m2.py')

def fun():
	from m1 import y
    priint(y)
x = 'm2'    

2.2 方案二

# m1.py
print('from m1.py')

y = 'm1'
from m2 import x

# m2.py
print('from m2.py')

x = 'm2'
from m1 import y

解决循环导入问题的方式:

​ 1.将循环导入的句式写在文件最下方

​ 2.函数内导入模块

__name__用法

# md.py
def index1():
    print('index1')
    
def index1():
    print('index2')
    
print(__name__)  # __main__

index1()
index2()

# run.py
import md

# run
# index1
# index2

当md.py被直接执行时,__name__打印的结果是__main__.而被当做模块直接运行的时候,会直接运行模块md中的f1()f2(),而__name__的结果是md,我们可以根据这个特点,将函数名加括号调用函数,放在if __name__ == '__main__':之下,即可以防止md被当做模块调用时,运行md里面的f1(),f2()

# md.py
def index1():
    print('index1')
    
def index1():
    print('index2')
    

if __name__=='__main__':

index1()
index2()

模块的查找路径

一、模块查找路径的顺序

模块其实就是一个文件,如果要执行文件、首先就需要找到模块的路径(某个文件夹)。如果模块的文件路径和执行文件不在同一文件目录下,我们就需要指定模块的路径。

模块的搜索路径指的就是在导入模块时需要检索的文件夹。

导入模块时查找模块的顺序是:

​ 1.先从内存中已经导入的模块中寻找

​ 2.内置的模块

​ 3.环境变量sys.path中找

import sys
print(sys.path)

['E:\\python10期\\练习\\练习\\day14', 'E:\\python10期\\练习\\练习', 'D:\\Program Files\\python3.7\\python37.zip', 'D:\\Program Files\\python3.7\\DLLs', 'D:\\Program Files\\python3.7\\lib', 'D:\\Program Files\\python3.7', 'D:\\Program Files\\python3.7\\lib\\site-packages', 'D:\\Program Files\\JetBrains\\PyCharm 2018.1.4\\helpers\\pycharm_matplotlib_backend']

sys.path是一个大列表,里面放了一对文件路径,第一个值永远是当前执行文件的所在的文件夹。

1.1 验证先从内存中找

如果我们咋运行run.py文件的时候,快速删除mmm.py文件,我们发现文件会继续运行,而不会报错,因为mmm已经被导入内存当中。如果我们再一次运行run.py时会报错,因为mmm.py已经被删除了。

# mmm.py

def f1():
    print('from mmm.py f1')

# run.py
import time
import mmm

time.sleep(5)
import mmm
mmm.f1()  # from mmm.py f1

1.2 验证先从内置中找

# time.py
print('from time.py')

# run.py
import time
print(time)  # # <module 'time' (built-in)>

1.3 验证从sys.path中找

如果md.py在E:\python10期\练习\text\day14路径下,而执行文件run.py路径为E:\python10期\练习\text,如果普通导入一定会报错,我们可以把模块路径E:\python10期\练习\text\day14添加到执行文件的环境变量sys.path中,防止报错。

注意:E:\python10期\练习\text\day14路径是与执行文件同级别的文件的路径。

import sys
sys.path.append(r'E:\python10期\练习\text\day14')
print(sys.path)

import md
md.index()


模块的查找顺序是以执行文件为准。而执行文件路径只能找到它的上一级目录。不识别其他的文件路径,所以需要将与执行文件同级的,模块在里面的文件夹路径添加到执行文件的。因为执行文件只能找到与其同级别的文件。

模块的绝对导入与相对导入

绝对导入必须依据执行文件所在的文件夹路径为准,绝对导入无论在执行文件中还是被导入文件都适用。

相对导入:

.代表的是当前文件的文件夹路径

..代表的是上一级路径

...代表的是上上一级路径

需要注意的是:相对导入不能在执行文件中使用,相对导入只能在被导入模块中使用,使用绝对导入,就不需要考虑。执行文件到底是谁,只需知道模块与模块之间路径关系。

相对导入与绝对导入

相对是对于当前目录而言的,先牢记.代表的是当前文件的文件夹路径,..代表的是上一级路径,...代表的是上上一级路径,比如以下结构:

ALL
|___A
|	|___Z
|		|___util.py
|		|___init.py
|___B
|	|__check.py
|___C
|	|___result.py
|___setup.py

  • A,B,C文件夹和setep.py文件都在ALL里。
  • zA里,util.pyinit.pyZ里。
  • check.pyB
  • resul.pyC

相对导入

  • util.py里导入init.py可以写为
  • import init(不用加.因为会先在当前目录下搜索)
  • from . import init util.py文件的文件夹是Z
  • from Z import init
  • util.py里导入check.py可以写为from ..B import check # util.py的上上一级文件夹是A,与A同级别的有B,C

绝对导入

  • util.py里导入init.py写为 import ALL.A.Z imprort init # 绝对导入
  • util.py里导入check.py可以写为from ALL.B import check
  • util.py里导入setup.py可以写为from ALL import check

软件开发目录规范

ATM/
|-- core/
|   |-- src.py  # 业务核心逻辑代码
|
|-- api/
|   |-- api.py  # 接口文件
|
|-- db/
|   |-- db_handle.py  # 操作数据文件
|   |-- db.txt  # 存储数据文件
|
|-- lib/
|   |-- common.py  # 共享功能
|
|-- conf/
|   |-- settings.py  # 配置相关
|
|-- bin/
|   |-- run.py  # 程序的启动文件,一般放在项目的根目录下,因为在运行时会默认将运行文件所在的文件夹作为sys.path的第一个路径,这样就省去了处理环境变量的步骤
|
|-- log/
|   |-- log.log  # 日志文件
|
|-- requirements.txt # 存放软件依赖的外部Python包列表,详见https://pip.readthedocs.io/en/1.1/requirements.html
|-- README  # 项目说明文件

bin/start.py

import sys
import os

BASE_DIR = os.path.dirname(os.path.dirname(__file__))
sys.path.append(BASE_DIR)
"""

pycharm会自动将你新建的最顶层的目录自动添加到环境变量中,
上面的两句话,不是针对你的,是针对下载你这个软件的用户
"""
from core import src

if __name__ == '__main__':
    src.run()
    
# 1,将core文件路径添加到system path中,
# 2,将ATM文件夹添加到system path中


posted @ 2019-07-16 20:31  最后的别离  阅读(182)  评论(0编辑  收藏  举报