【Python】 关于import和package结构
关于import语句
python程序需要使用某个第三方模块的话要用import语句,其实就是把目标模块的内容加载到内存里。当然,在加载之前,python会按照一定的顺序寻找sys.path中的目录。sys.path中的第一项非常不起眼是'',但是千万要注意这是指当前工作目录。也就是说,如果当前工作目录下恰好有和你想import的模块名同名脚本的话,python是优先把那个脚本import进来,然后就停止搜索了。python中所有加载到内存中的模块都会记录到sys.modules中,这是一个模块名和模块文件的对应字典。当语句中出现import语句的时候,python首先会到这个字典中去找,如果找不到,就搜索sys.path给出的一些路径,寻找相关模块。找到之后就把它加入内存并载入sys.modules。需要注意的是import A.B的时候,解释器会先寻找A模块,然后再寻找A.B,A模块和A.B模块都会被记录到sys.module中去。
在确认内存中已经加载了相关模块之后,python就会把这个模块名保存到local的命名空间中,如果有import...as..的话就以as为准。不过即使有as的时候,加载到sys.modules里面去的仍然是真正的模块名。
对于 from A import B这样的形式的话,python首先在sys.modules中找模块A.B,找到的话就获得了这个模块的对象而直接加载,没找到的话就新建一个空的对象<module xx>。再获取这个对象的__dict__(当然,一个空对象的__dict__肯定也是空的,所以python会先解析模块名.py来充实__dict__。),从中寻找相关子方法或子对象的信息,找不到则报错。
*__dict__是一个类或者实例的固有属性,其内容是其所有成员属性和成员方法的名字以及内容的对应。
比如下面这样一个示例:
#在A.py中:
from B import D
class C:
pass #在B.py中:
from A import C
class D:
pass
这样两个模块的话,先python A.py 来执行前者,首先python建立B.py的空对象,尝试解析B.py对象的__dict__,当解析到B的第一行时,又建立了A.py的空对象,然后为了得到A.py的__dict__又跑回去解析A.py。那么又碰到了A的第一句,但此时B.py的对象已经在内存中存在,但是它的__dict__仍然是空的没有被充实,所以最终会报ImportError:无法找到D
如果是import一整个包而不是一个文件的话,那就是解析这个包里的__init__.py文件。
■ 注意文件本身不能和module名重名
今天写了一个email.py脚本里import了email模块,然后如果就在存放email.py的这个脚本的目录下运行它就报错了。这是因为python的工作目录是email.py存放的目录时,python优先搜索当前工作目录下的模块名(见上面的说明)
■ python包结构简单介绍
在构建python包的时候,我们经常会看到__init__.py这个文件。含有__init__.py这个文件的目录可以认为是一个python的package,可以被其他文件导入的。一个简单的包就是下面这样子的:
#######目录结构
# ./
# | test.py 这个是我们用来测试导入包的脚本,是__main__模块
# | main/ 测试中用到的包
# | __init__.py 这里面现在是空的,这个文件只为了表明main是个package
# | funcs.py 这里面定义一个函数func()供外部调用
######## ####test.py中####
import main
import main.funcs
from main.funcs import func
#import main.funcs.func
首先可以看出来,因为__init__.py的存在,main是一个包,所以在外界使用是可以直接import它的包名。以上前三个语句都是可以正确执行的,最后一个被注释的语句会报错,这说明在import语句中用圆点符来表示层级的时候最低只能表示到模块层(或者脚本这一层),而不能深入到变量或者函数层。如果想指定某个变量或者函数直接导入,那么就要用from xx import xx这样的形式了。在第两句那样的情况中,为了使用func,我们可以main.funcs.func来调用,所以说在普通语句中的原点符可以深入到变量或者函数层级中。但是在只有第一句的情况下,我们可能没有办法调用到func,因为此时写main.funcs.func是会报错的,中间那一节funcs并不会随着import main这个语句加载进来,具体可以在Import main之后dir(main)或者看下main.__dict__来看,是看不到funcs这个的。那么是不是说import main一无是处了呢?也不见得:
我们再回头仔细看__init__.py这个文件,刚才假定了这个文件是空的,其实这里面也可以写很多东西,比如我在__init__.py中增加了一个名为var_p的变量。那么在test.py中要如何使用这个变量?很简单,就是import main然后再调用main.var_p,或者直接from main import var_p这个var_p除了是一个变量,也可以是一个函数,一个类等等。这就是说,在__init__.py这个文件中写的东西,被视为“包直属”的一些内容,所以通过包名main来直接调用。反过来说,__init__.py其实是在别人调用包名时指向的那个文件。比如此时我们再查看dir(main)就可以看到var_p的存在了。
另外在这样的情景下还有一个小问题,加入__init__.py中定义了一个函数叫funcs,然后main包中又有一个模块叫funcs,引用这两个funcs好像都是用main.funcs。在import的时候我们可以区分开他们,因为作为函数的那个funcs是不能直接被import main.funcs的,而在from main import funcs的时候因为搜索命名空间的就近原则,导入的是函数funcs(前提是在此之前不能导入过main.funcs,否则会因为一些原因导致最终还是导入module的funcs,至于这个原因是什么,请看上面关于from import 和import 机制上的一个区别)。在使用的时候,一般情况下模块会是main.funcs来调用而函数会是funcs来调用,那如果在import模块的时候用了import main.funcs as funcs呢?这个的话就牵扯到命名空间的覆盖,如果先from main import funcs后然后再import main.funcs as funcs的话,模块funcs会覆盖函数funcs在命名空间中的地位,所以最终的funcs是模块funcs。再深入问一个,假如刚才两句语句反过来呢??从结果上来说没什么好说的,因为反过来即使没有as关键字的时候,funcs也是模块funcs。有了as关键字之后,尽管命名空间中会多出一个funcs来,但是加载到sys.modules里面去的仍然是main.funcs,所以from import 的时候依然会先找到模块的main.funcs,所以from import的结果依然是模块funcs。
python的package之间还可以进行嵌套,比如可以有一个叫app的package,这意味着存在app/__init__.py这个文件,然后app下面可以有个子目录叫main,main里有个__init__.py,这样main就算是一个子package,但也是一个package。嵌套包含关系的包自然就需要用.来导入,比如上面说的就可以import app.main这样的感觉来导入。类似的,import 进来的app.main其实是代表了main/__init__.py这个文件,app.main.var可以访问这个init文件中定义的叫var的变量or函数or类等。
■ 关于包内导入和relative import
上面讲了一大堆比较复杂,究其复杂的原因,就是因为我们没有办法通过main.funcs.func来直接调用包中某个模块中的某个对象。其实聪明的同学可能能想到,之前我们在__init__.py里手动定义了一个funcs可以直接拿来用,那么可不可以把模块funcs也弄到__init__.py里面去。这样不就能直接用了。很自然,就会想到在__init__.py中写上 import funcs即可。这个就是包内导入了(__init__.py和funcs.py在同一个包内)。这是一个最为简单的包内导入的例子,如果想要用from import 的形式来import funcs这个模块的话,可以这么写:from . import funcs。这个“.”在这里,代表的就是当前目录的意思,而这种导入方法就是所谓的relative import 即相对导入了。
和所有“相对”和“绝对”的东西一样,这个相对导入更加灵活。在from后面加一个点就是指当前目录,加上两个点就是从上级目录导入。如果在点后面直接接上某个名字那就是说从点的数量指向的那个目录下找到相应名字的模块,from这个模块去import一些东西。
有时候,我们在自己自定义的包里里面可能碰巧有和内建模块重名的文件出现,此时在导入的时候解释器可能会闹不清该导入什么模块,此时可以用相对导入来明确指出我需要的是我自己定义的模块而不是内建的。(然而绝对导入时似乎也是这样的。。不知道为甚网上很多人都这么写,闹得不太清楚。。)
需要注意的是,使用了相对导入的文件只能作为module来用,而不能作为__main__模块来直接运行,否则会报Attempted relative import in non-package错。这是因为相对导入时找路径是通过模块名__name__去找的,如果模块名不是脚本名而是__main__了那么自然是找不到了。
【Python】 关于import和package结构的更多相关文章
- Python Import机制备忘-模块搜索路径(sys.path)、嵌套Import、package Import
出处:http://blog.csdn.net/kernelspirit/article/details/3381666 最近在看<Python源码剖析>,对Python内部运行机制比以前 ...
- [转] Python的import初探
转载自:http://www.lingcc.com/2011/12/15/11902/#sec-1 日常使用python编程时,为了用某个代码模块,通常需要在代码中先import相应的module.那 ...
- 解析Python编程中的包结构
解析Python编程中的包结构 假设你想设计一个模块集(也就是一个"包")来统一处理声音文件和声音数据.通常由它们的扩展有不同的声音格式,例如:WAV,AIFF,AU),所以你可能 ...
- python 的import机制2
http://blog.csdn.net/sirodeng/article/details/17095591 python 的import机制,以备忘: python中,每个py文件被称之为模块, ...
- python之import机制
1. 标准 import Python 中所有加载到内存的模块都放在 sys.modules .当 import 一个模块时首先会在这个列表中查找是否已经加载了此模块,如果加载了则只是将 ...
- 关于Python的import机制原理
很多人用过python,不假思索地在脚本前面加上import module_name,但是关于import的原理和机制,恐怕没有多少人真正的理解.本文整理了Python的import机制,一方面自己总 ...
- package结构
1.package结构 一个package下常见的文件. 路径有:├── CMakeLists.txt #package的编译规则(必须)├── package.xml #package的描述信息(必 ...
- Python如何import文件夹下的文件
Python的import包含文件功能就跟PHP的include类似,但更确切的说应该更像是PHP中的require,因为Python里的import只要目标不存在就报错程序无法往下执行.要包含目录里 ...
- python 的 import 使用规则
对于含有 __init__.py 的目录(如adir),其实它就是一个package,它的子目录如果也包含 __init__.py,则只要将 adir 加入 sys.path,则它的字目录就不用加了, ...
随机推荐
- JavaScript 使用闭包防止变量污染
javaScript在多人协作时,如果定义过多的全局变量 有可能造成全局变量命名冲突,使用闭包来解决功能对变量的调用 将变量写到一个独立的空间里面 就是闭包里面 var name = "外部 ...
- R︱shiny实现交互式界面布置与搭建(案例讲解+学习笔记)
要学的东西太多,无笔记不能学~~ 欢迎关注公众号,一起分享学习笔记,记录每一颗"贝壳"~ --------------------------- 看了看往期的博客,这个话题竟然是第 ...
- eclipse生成【带有外部jar包】的java可执行jar包
之前有写过一篇使用eclipse生成java可执行jar包,但是最近的一次使用中无论如何都不成功,当双击执行打成的jar时,弹出如下错误: could not find the main class: ...
- Android常见Crash类型分析(一)
问题1. java.lang.IllegalStateException: The specified child already has a parent. You must call remo ...
- 小说接入UC浏览器内核技术对话(二)
质辛@灿岩 质辛跟我们说一下那个删除文件的逻辑吧质辛@灿岩 应该不是删除cache下所有文件吧?质辛质辛@智鹰 提供一下我们的临时文件完整路径给 灿岩吧质辛@智鹰 是负责我们ucsdk的 技术对 ...
- 如何修改WinPE Boot的.wim镜像文件
1. 使用imagex /apply或imagex /mountrw将WIM镜像文件mount到某个文件夹,假设为d:\tmp\winpe_x86\mount. 例: imagex /mountrw ...
- JavaScript的几种常见的创建方式
1.通过Object构造函数或者对象字面量创建单个对象 使用字面量方法创建对象:var stut = {name: "张三"}; 使用内置构造函数创建对象:var stu = ne ...
- 【原】eclipse创建maven工程时,如何修改默认JDK版本?
问题描述:eclipse建立maven项目时,JDK版本默认是1.5,想创建时默认版本设置为1.8,如何修改? 解决方案: 找到本机maven仓库存放位置,比如:${user.home}/.m2/路径 ...
- freemarker中的split字符串分割(十六)
1.简易说明 split分割:用来根据另外一个字符串的出现将原字符串分割成字符串序列 2.举例说明 <#--freemarker中的split字符串分割--> <#list &quo ...
- 使用 github 做代码管理,知道这些就够了
只要掌握了下面的常用命令,基本上用使用 github 就没有问题.github 有两种认证方式,一种是通过 ssh 私钥的方式,一种通过 https 的账号名和密码.ssh 方式需要创建本地秘钥并且添 ...