python模块和包

模块

在计算机程序开发的过程中,在一个文件里写代码就会越来越长,越来越不容易维护,所以为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,能够将程序中经常要用的功能集合到一起,然后在想用的地方随时导入使用,这几乎就是模块的全部含义了。

 

最后总结:

模块就是一组功能的集合体,我们的程序可以导入模块来复用模块里的功能

在python中,模块的使用方式都是一样的,但其实细说的话,模块可以分为四个通用类别:   

 1 使用python编写的.py文件  (在python中,一个.py 文件就是一个模块(module))

 2 已被编译为共享库或DLL的C或C++扩展  

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

4 使用C编写并链接到python解释器的内置模块

为什么是使用模块:

#1、从文件级别组织程序,更方便管理

随着程序的发展,功能越来越多,为了方便管理,我们通常将程序分成一个个的文件,这样做程序的结构更清晰,方便管理。这时我们不仅仅可以把这些文件当做脚本去执行,还可以把他们当做模块来导入到其他的模块中,实现了功能的重复利用

#2、拿来主义,提升开发效率

同样的原理,我们也可以下载别人写好的模块然后导入到自己的项目中使用,这种拿来主义,可以极大地提升我们的开发效率

特别说明:

如果你退出python解释器然后重新进入,那么你之前定义的函数或者变量都将丢失,因此我们通常将程序写到文件中以便永久保存下来,需要时就通过python test.py方式去执行,此时test.py被称为脚本script。

#编写好的一个python文件可以有两种用途:
    一:脚本,一个文件就是整个程序,用来被执行
    二:模块,文件中存放着一堆功能,用来被导入使用


#python为我们内置了全局变量__name__,
    当文件被当做脚本执行时:__name__ 等于'__main__'
    当文件被当做模块导入时:__name__等于模块名

#作用:用来控制.py文件在不同的应用场景下执行不同的逻辑
    if __name__ == '__main__':

模块分为三种:

  • 自定义模块
  • 内置标准模块(又称标准库) 执行help('modules') 查看所有python 自带的模块列表
  • 开源模块(第三方) 可以通过pip install  联网安装

模块的调用

 

创建自定义模块时,要注意:

 

  • 模块名要遵循Python变量命名规范,不要使用中文、特殊字符;
  • 模块名不要和系统模块名冲突,最好先查看系统是否已存在该模块,检查方法是在Python交互环境执行import xxx,若成功则说明系统存在此模块

 

import module

import module as xx

from module import xx

from module import xx as xx

from module.xx import xx

from module import * (不推荐用,后面解释)

import语句是可以在程序中的任意位置使用的

python 所有加载的模块信息都存放在 sys.modules 结构中,当 import 一个模块时,会按如下步骤来进行
如果是 import A,检查 sys.modules 中是否已经有 A,如果有则不加载,如果没有则为 A 创建 module 对象,并

 

加载 A 如果是 from A import B,先为 A 创建 module 对象,再解析A,从中寻找B并填充到 A 的 __dict__ 中

查找的顺序后面解释

 

我们可以从sys.modules中找到当前已经加载的模块

 

注意: 模块一旦被调用,就相当于执行了模块(文件)里面的代码

针对同一个模块为了防止你重复导入,python的优化手段是:第一次导入后就将模块名加载到内存了,

后续的import语句仅是对已经加载到内存中的模块对象增加了一次引用,不会重新执行模块内的语句,会直接引用内存中已经加载好的结果

被导入模块有独立的名称空间

每个模块都是一个独立的名称空间,定义在这个模块中的函数,把这个模块的名称空间当做全局名称空间,

这样我们在编写自己的模块时,就不用担心我们定义在自己模块中全局变量会在被导入时,与使用者的全局变量冲突

from module import xx

frommoduleimport xx as xx(别名)

唯一的区别就是:使用from...import...则是将spam中的名字直接导入到当前的名称空间中,所以在当前名称空间中,直接使用名字就可以了、无需加前缀:spam.
#from...import...的方式有好处也有坏处
    好处:使用起来方便了
    坏处:容易与当前执行文件中的名字冲突,覆盖掉

#from spam import * 把spam中所有的不是以下划线(_)开头的名字都导入到当前位置

#大部分情况下我们的python程序不应该使用这种导入方式,因为*你不知道你导入什么名字,很有可能会覆盖掉你之前已经定义的名字。
而且可读性极其的差,在交互式环境中导入时没有问题。
可以使用__all__来控制*(用来发布新版本),在spam.py中新增一行
__all__=['money','read1'] #这样在另外一个文件中用from spam import *就这能导入列表中规定的两个名字
 
 

比如 在当前导入

import gettrace 这个模块

但是

你又运行了

from sys import * 

dir()

哈哈,2B 了,这两冲突了,你写的模块让覆盖了

模块搜索路径

模块的查找顺序是:内存中已经加载的模块->内置模块->sys.path路径中包含的模块

#模块的查找顺序
1、在第一次导入某个模块时(比如spam),会先检查该模块是否已经被加载到内存中(当前执行文件的名称空间对应的内存),如果有则直接引用
ps:python解释器在启动时会自动加载一些模块到内存中,可以使用sys.modules查看,也可以用dir()查看
sys.modules是一个字典,内部包含模块名与模块对象的映射,该字典决定了导入模块时是否需要重新导入。 2、如果没有,解释器则会查找同名的内建模块 3、如果还没有就从sys.path给出的目录列表中依次寻找spam.py文件。 #sys.path的初始化的值来自于: The directory containing the input script (or the current directory when no file is specified). PYTHONPATH (a list of directory names, with the same syntax as the shell variable PATH). The installation-dependent default. #需要特别注意的是:我们自定义的模块名不应该与系统内置模块重名。虽然每次都说,但是仍然会有人不停的犯错。 #在初始化后,python程序可以修改sys.path,路径放到前面的优先于标准库被加载。
sys.path从以下位置初始化
    1 执行文件所在的当前目录
    2 PTYHONPATH(包含一系列目录名,与shell变量PATH语法一样)
    3 依赖安装时默认指定的
1 >>> import sys 2 >>> sys.path.append('/a/b/c/d') 3 >>> sys.path.insert(0,'/x/y/z')
#排在前的目录,优先被搜索 注意:搜索时按照sys.path中从左到右的顺序查找,位于前的优先被查找,sys.path中还可能包含.zip归档文件和.egg文件,
python会把.zip归档文件当成一个目录去处理, #首先制作归档文件:zip module.zip foo.py bar.py import sys sys.path.append('module.zip') import foo,bar #也可以使用zip中目录结构的具体位置 sys.path.append('module.zip/lib/python') #windows下的路径不加r开头,会语法错误 sys.path.insert(0,r'C:\Users\Administrator\PycharmProjects\python') #至于.egg文件是由setuptools创建的包,这是按照第三方python库和扩展时使用的一种常见格式,.egg文件实际上只是添加了额外元数据(如版本号,依赖项等)的.zip文件。 #需要强调的一点是:只能从.zip文件中导入.py,.pyc等文件。使用C编写的共享库和扩展块无法直接从.zip文件中加载(此时setuptools等打包系统有时能提供一种规避方法),
从.zip中加载文件不会创建.pyc或者.pyo文件,因此一定要事先创建他们,来避免加载模块时性能下降。
#官网链接:https://docs.python.org/3/tutorial/modules.html#the-module-search-path
注意:在支持软连接的文件系统中,执行脚本所在的目录是在软连接之后被计算的,换句话说,包含软连接的目录不会被添加到模块的搜索路径中 在初始化后,我们也可以在python程序中修改sys.path,执行文件所在的路径默认是sys.path的第一个目录,在所有标准库路径的前面。
这意味着,当前目录是优先于标准库目录的,需要强调的是:我们自定义的模块名不要跟python标准库的模块名重复,除非你是故意的,只能说你是傻叉

编译python文件(了解)

为了提高加载模块的速度,强调强调强调:提高的是加载速度而绝非运行速度。python解释器会在__pycache__目录中下缓存每个模块编译后的版本,格式为:module.version.pyc。通常会包含python的版本号。例如,在CPython3.3版本下,spam.py模块会被缓存成__pycache__/spam.cpython-33.pyc。这种命名规范保证了编译后的结果多版本共存。

Python检查源文件的修改时间与编译的版本进行对比,如果过期就需要重新编译。这是完全自动的过程。并且编译的模块是平台独立的,所以相同的库可以在不同的架构的系统之间共享,即pyc使一种跨平台的字节码,类似于JAVA火.NET,是由python虚拟机来执行的,但是pyc的内容跟python的版本相关,不同的版本编译后的pyc文件不同,2.5编译的pyc文件不能到3.5上执行,并且pyc文件是可以反编译的,因而它的出现仅仅是用来提升模块的加载速度的,不是用来加密的

#python解释器在以下两种情况下不检测缓存
#1 如果是在命令行中被直接导入模块,则按照这种方式,每次导入都会重新编译,并且不会存储编译后的结果(python3.3以前的版本应该是这样)
    python -m spam.py

#2 如果源文件不存在,那么缓存的结果也不会被使用,如果想在没有源文件的情况下来使用编译后的结果,则编译后的结果必须在源目录下 sh-3.2# ls __pycache__ spam.py sh-3.2# rm -rf spam.py sh-3.2# mv __pycache__/spam.cpython-36.pyc ./spam.pyc sh-3.2# python3 spam.pyc  #提示: 1.模块名区分大小写,foo.py与FOO.py代表的是两个模块 2.你可以使用-O或者-OO转换python命令来减少编译模块的大小 -O转换会帮你去掉assert语句 -OO转换会帮你去掉assert语句和__doc__文档字符串 由于一些程序可能依赖于assert语句或文档字符串,你应该在在确认需要 的情况下使用这些选项。 3.在速度上从.pyc文件中读指令来执行不会比从.py文件中读指令执行更快,只有在模块被加载时,.pyc文件才是更快的 4.只有使用import语句是才将文件自动编译为.pyc文件,在命令行或标准输入中指定运行脚本则不会生成这类文件,
因而我们可以使用compieall模块为一个目录中的所有模块创建.pyc文件 模块可以作为一个脚本(使用python -m compileall)编译Python源 python -m compileall /module_directory 递归着编译 如果使用python -O -m compileall /module_directory -l则只一层 命令行里使用compile()函数时,自动使用python -O -m compileall 详见:https://docs.python.org/3/library/compileall.html#module-compileall

模块的重载 (了解)

考虑到性能的原因,每个模块只被导入一次,放入字典sys.module中,如果你改变了模块的内容,你必须重启程序,

python不支持重新加载或卸载之前导入的模块,有的同学可能会想到直接从sys.module中删除一个模块不就可以卸载了吗,注意了

,你删了sys.module中的模块对象仍然可能被其他程序的组件所引用,因而不会被清除。

 特别的对于我们引用了这个模块中的一个类,用这个类产生了很多对象,因而这些对象都有关于这个模块的引用。

如果只是你想交互测试的一个模块,使用 importlib.reload(), e.g. import importlib; importlib.reload(modulename),这只能用于测试环境。

 

 开源模块的安装,使用

https://pypi.python.org/pypi 是python的开源模块库,到目前为止全世界的开发者贡献了133655个模块,几乎包括所有功能,能帮助你用python做任何事,只要你注册一个帐号就可以往这个平台上上传你自己的模块,这样全世界的开发者都可以用你的模块。

下载安装有两种方式:

1.源码安装

下载源码

解压源码

进入目录

编译源码 python setup.py build

安装源码 python setup.py install

注:在使用源码安装时,需要使用到gcc编译和python开发环境,所以,需要先执行(linux 系统(redhat,centos)):

yum install gcc
yum install python - devel(默认是2.x的版本),如果需要3的版本,自己源码安装。
2. pip 安装
pip install paramiko (默认是官网,官网在国外速度慢,如果比较大的包推荐用国内镜像)
国内安装源安装
pip install paramiko -i http://pypi.douban.com/ simple
如果有如下报错:
 
pip install paramiko -i http://pypi.douban.com/simple --trusted-host pypi.douban.com
下载后可以直接使用 因为自动安装到 python/site-packages下面了
 
如果想配置成默认的源,方法如下:
需要创建或修改配置文件(一般都是创建),

linux的文件在~/.pip/pip.conf,
windows在%HOMEPATH%\pip\pip.ini),
修改内容为:

[global]
index-url = http://pypi.douban.com/simple
[install]
trusted-host=pypi.douban.com

 
这样在使用pip来安装时,会默认调用该镜像。

临时使用其他源安装软件包的python脚本如下:

#!/usr/bin/python
  import os
  package = raw_input ( "Please input the package which you want to install!\n" )
command = "pip install %s -i http://pypi.mirrors.ustc.edu.cn/simple --trusted-host pypi.mirrors.ustc.edu.cn" % package
os.system(command)
也可以使用读入文件进行安装。
ok,记录一下,以便于后期查阅!
批量安装模块,怎么安装

pip freeze or pip list 这个是列出所有安装过的模块

批量安装,需要把安装的模块都写到一个文件里面requirements.txt

文件的内容:

APScheduler==2.1.2
Django==1.5.4 MySQL-Connector-Python==2.0.1 MySQL-python==1.2.3 PIL==1.1.7 South==1.0.2 django-grappelli==2.6.3 django-pagination==1.0.7

然后通过pip 命令安装

pip install -r requirements.txt

这样就可以批量安装所有依赖的模块。

更多pip的命令

安装本地安装模块

pip install <目录>/<文件名> 或 pip install --use-wheel --no-index --find-links=wheelhouse/ <模块>

<模块>前有空格

可简写为

pip install --no-index -f=<目录>/ <模块>

卸载模块

pip uninstall <包名> 或 pip uninstall -r requirements.txt

升级模块

pip install -U <包名>

或:pip install <包名> --upgrade

升级pip

pip install -U pip

显示模块所在的目录

pip show -f <模块名>

搜索模块

pip search <搜索关键字>

查询可升级的模块

pip list -o

下载模块而不安装

pip install <模块> -d <目录> 或 pip install -d <目录> -r requirements.txt

打包

pip wheel <模块名>

 


当你的模块文件越来越多,就需要对模块文件进行划分,比如把负责跟数据库交互的都放一个文件夹,把与页面交互相关的放一个文件夹

像上面这样,一个文件夹管理多个模块文件,这个文件夹就被认为是包。

官网解释

Packages are a way of structuring Python’s module namespace by using “dotted module names”

包是一种通过使用‘.模块名’来组织python模块名称空间的方式。

具体的:包就是一个包含有__init__.py文件的文件夹,所以其实我们创建包的目的就是为了用文件夹将文件/模块组织起来

#需要强调的是:
  1. 在python3中,即使包下没有__init__.py文件,import 包仍然不会报错,而在python2中,包下一定要有该文件,否则import 包报错
  2. 创建包的目的不是为了运行,而是被导入使用,记住,包只是模块的一种形式而已,包的本质就是一种模块
       3.关于包相关的导入语句也分为import和from ... import ...两种,但是无论哪种,无论在什么位置,在导入时都必须遵循一个原则:
      凡是在导入时带点的,点的左边都必须是一个包,否则非法。可以带有一连串的点,如item.subitem.subsubitem,但都必须遵循这个原则。
      但对于导入后,在使用时就没有这种限制了,点的左边可以是包,模块,函数,类(它们都可以用点的方式调用自己的属性)。
      4.import导入文件时,产生名称空间中的名字来源于文件,import 包,产生的名称空间的名字同样来源于文件,即包下的__init__.py,

     导入包本质就是在导 入该文件
    5.包A和包B下有同名模块也不会冲突,如A.a与B.a来自俩个命名空间

如果按上面的目录结果,没有__init__.py 的文件在python2运行时会报错

所以,需要在目录下面建立一个_init_.py 的文件。该文件可以为空,用于标识当前的文件夹是一个包。

你在用IDE 建立工程目录的时候,可以指定建立包,默认就带__init__.py 的文件:

既然有包的概念了,这样我们就遇到一个问题,目录之间怎么调用模块呢。

现在目录结构是

 根据上面的结构,如何实现 crm/views.py 里导入my_proj/settings.py的模块

如果直接导入肯定会报错。

Traceback (most recent call last):
  File "C:/工作/app/python/my_proj/crm/views.py", line 3, in <module>
    from my_proj import settings
ImportError: cannot import name 'settings'

因为路径找不到,前面已经讲过了,sys.path就没有这个目录结构。只能导入同级别或者子级别目录的模块。

通过前面的知识,我们知道,如果通过增加环境变量,是可以实现的,因为sys.path 是一个列表,所以,我通过

修改列表来导入这个模块

 

还有如果通过views.py 当作单人口,如果在settings.py 文件里面import urls 就会报错,解释这种情况,这里面不在解释了。

这样就实现了不同目录的调用,但是感觉这种模式太low 了

这时,我想起了 linux cd ..  cd ../.. 这样对比了一下,发现pyhon 里面,导入也可以通过这种相对路径来导入模块

但是必须满足两个条件

1.文件夹必须有__init__.py文件。

2.不能作为顶层模块来执行目录中的py文件

怎么解释呢

Python 相对导入与绝对导入,这两个概念是相对于包内导入而言的。包内导入即是包内的模块导入包内部的模块。

绝对导入的格式为 import A.B 或 from A import B,相对导入格式为 from . import B 或 from ..A import B,.代表当前模块,..代表上层模块,...代表上上层模块,依次类推。

相对导入可以避免硬编码带来的维护问题,例如我们改了某一顶层包的名,那么其子包所有的导入就都不能用了。但是 存在相对导入语句的模块,

不能直接运行,否则会有异常:

ValueError: Attempted relative import in non-package

或者是

ValueError: attempted relative import beyond top-level package

这是什么原因呢?我们需要先来了解下导入模块时的一些规则:

在没有明确指定包结构的情况下,Python 是根据 __name__ 来决定一个模块在包中的结构的,如果是 __main__ 则它本身是顶层模块,没有包结构,如果是A.B.C 结构,那么顶层模块是 A。基本上遵循这样的原则:
如果是绝对导入,一个模块只能导入自身的子模块或和它的顶层模块同级别的模块及其子模块
如果是相对导入,一个模块必须有包结构且只能导入它的顶层模块内部的模块
如果一个模块被直接运行,则它自己为顶层模块,不存在层次结构,所以找不到其他的相对路径

Python2.x 缺省为相对路径导入,Python3.x 缺省为绝对路径导入。

绝对导入可以避免导入的包覆盖掉标准库模块(由于名字相同,发生冲突)。

如果在 Python2.x 中要默认使用绝对导入,可以在文件开头加入如下语句:   
from __future__ import absolute_import
这句 import 并不是指将所有的导入视为绝对导入,而是指禁用

implicit relative import(隐式相对导入)

但并不会禁掉

explicit relative import(显示相对导入)。

那么到底什么是隐式相对导入,什么又是显示的相对导入呢?我们来看一个例子,假设有如下包结构:
    
thing
├── books
│ ├── adventure.py
│ ├── history.py
│ ├── horror.py
│ ├── __init__.py
│ └── lovestory.py
├── furniture
│ ├── armchair.py
│ ├── bench.py
│ ├── __init__.py
│ ├── screen.py
│ └── stool.py
└── __init__.py

那么如果在 stool 中引用 bench,则有如下几种方式:

import bench     # 此为 implicit relative import 隐式相对导入
from . import bench   # 此为 explicit relative import 显示相对导入
from furniture import bench # 此为 absolute import 决对导入

隐式相对就是没有告诉解释器相对于谁,但默认相对与当前模块;而显示相对则明确告诉解释器相对于谁来导入。

以上导入方式的第三种,才是官方推荐的,第一种是官方强烈不推荐的,Python3 中已经被废弃,这种方式只能用于导入 path 中的模块。

相对与绝对仅针对包内导入而言

最后再次强调,相对导入与绝对导入仅针对于包内导入而言,要不然本文所讨论的内容就没有意义

所谓的包,就是包含 __init__.py 文件的目录,该文件在包导入时会被首先执行,该文件可以为空,也可以在其中加入任意合法的 Python 代码。

相对导入可以避免硬编码,对于包的维护是友好的。绝对导入可以避免与标准库命名的冲突,实际上也不推荐自定义模块与标准库命令相同。

前面提到含有相对导入的模块不能被直接运行,实际上含有绝对导入的模块也不能被直接运行,会出现 ImportError:
ImportError: No module named XXX

这与绝对导入时是一样的原因。要运行包中包含绝对导入和相对导入的模块,可以用 python -m A.B.C 告诉解释器模块的层次结构。

有人可能会问:假如有两个模块 a.py 和 b.py 放在同一个目录下,为什么能在 b.py 中 import a 呢?

这是因为这两个文件所在的目录不是一个包,那么每一个 python 文件都是一个独立的、可以直接被其他模块导入的模块,就像你导入标准库一样,它们不存在相对导入和绝对导入的问题。

包以及包所包含的模块都是用来被导入的,而不是被直接执行的。而环境变量都是以执行文件为准的

所以相对导入与绝对导入仅用于包内部

 

 

 

转载于:https://www.cnblogs.com/centos-python/articles/8664390.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值