Python 进阶_模块 & 包

目录

模块的搜索路径和路径搜索

默认的模块搜索路径在 Python 解析器编译安装时被指定,我们可以通过 sys 模块来查看和修改它:

In [4]: sys.path
Out[4]: 
['',
 '/usr/bin',
 '/usr/lib/python2.7',
 '/usr/lib/python2.7/plat-x86_64-linux-gnu',
 '/usr/lib/python2.7/lib-tk',
 '/usr/lib/python2.7/lib-old',
 '/usr/lib/python2.7/lib-dynload',
 '/usr/local/lib/python2.7/dist-packages',
 '/opt/stack/keystone',
 '/opt/stack/glance',
 '/opt/stack/cinder',
 '/opt/stack/nova',
 '/opt/stack/horizon',
 '/opt/stack/tempest',
 '/opt/stack/oslo.vmware',
 '/usr/lib/python2.7/dist-packages',
 '/usr/lib/python2.7/dist-packages/IPython/extensions']

存在于搜索路径列表的路径下的模块就可以直接被 import 了。我们还能够通过 sys.models 可以找到当前导入了那些模块和它来自萨满路径和地方。

NOTE:这个路径每个人都不尽相同,在安装 OpenStack 项目时,每一个项目的根目录都会加入到该目录列表中。

命名空间和变量作用域的比较

命名空间:是变量名(标识符)到实际对象的映射,是一个字典。Python 的命名空间类型有内建命名空间、全局命名空间、嵌套命名空间、局部命名空间等类型。

Python 解析器启动时,首先会加载内建命名空间,该空间的变量名映射被包含在 __builtin__ 模块中,我们可以通过 __builtin__.__dict__ 来查看其包含的变量字典,主要包含了 ERROR、内建函数等类型。

NOTE__builtin__ 模块和 __builtins__ 模块的名称非常相似,在标准的 Python 环境中 __builtins__ 含有 __builtin__ 内的所有变量名映射,区别在于 __builtins__ 是可以被修改的,用于创建一个沙盒环境,但少有人用,两者并不相等。

作用域:变量名有效的区域定义,指定了一个变量名在一个指定的区域内有效。

两者的区别:作用域和命名空间非常相似,一个作用域的创建伴随着新的命名空间的生成,其一般是具有对应的关系。所以往往命名空间中含有的变量名在对应的作用域中都是有效的,但本质的区别是:命名空间是纯粹意义上的变量名和对象的映射关系,而作用域还指出了在代码中的那个物理位置(模块内、类内、函数类)可以访问到命名空间的变量名,从而得到其映射的对象。

无限制的命名空间:Python 的一个特性在于我们可以动态的为一个函数对象添加变量,这种变量也称之为实例属性。所以 Python 的命名空间是无限制的。

In [9]: def func():
   ...:     print "This is a func"
   ...:     

In [10]: func.__dict__
Out[10]: {}

In [13]: func.name = 'JMilkFan'

In [14]: func.__dict__
Out[14]: {'name': 'JMilkFan'}

变量名的查找/覆盖

当我们 call 一个变量名时,Python 解析器的查找顺序如下:

  1. 先从局部命名空间开始找,如果找到了,则引用变量名映射的对象。若没有找到,则进行下一步。
  2. 到嵌套命名空间找,如果没有找到,则进行下一步。
  3. 到全局命名空间找,如果没有找到,则进行下一步。
  4. 到内置命名空间找,如果没有找到,则进入下一步。

从这个变量名查找算法可以看出,当我们在代码物理位置定义了一个变量时,这个变量就会添加到命名空间中。而且这个命名空间为变量名的查找提供了清单。更重要的一点是这个查找算法还有效的覆盖了外层作用域的同名变量。“局部作用域” 覆盖 “嵌套作用域” 覆盖 “全局作用域” 覆盖 “内置作用域”。

导入模块

import 语句

import module1[, module2, ..., moduleN]

Python 允许一行导入多个模块,但那这并不符合 PEP8 的编程标准,所以建议使用多行导入的方式。

导入模块类型的顺序(中间已单空行隔开)

  1. Python 标准库模块。
  2. Python 第三方模块。
  3. Python 自定义模块。

NOTE:模块的导入遵循作用域的原则,在顶格导入的模块,那么模块的作用域是全局的。在函数或类内导入的模块,其作用域就是局部或嵌套作用域。

from-import 语句

如果你希望从一个模块中导入指定属性,或希望从一个 package 中导入一个模块,都可以使用 form-import 语句。

from-import 语句也支持一行导入多个模块或属性:

from package import (module1, module2, module3, ..., moduleN)

from-import 语句的好处在于可以针对性的导入可以提高效率和节省空间。

NOTE:我们一般建议导入到模块即可,非特殊情况不要导入包或模块内的属性。

扩展的 import 语句 as

as 也称之为别名,模块之间也会经常出现同名的情况,如果出现同一个模块中导入了两个或多个同名模块的情况,那么应该为每个相同的模块取一个别名来有效区分。

除此之外,如果模块的全称过长,我们也可以为其取一个短小的别名。

import module1 as module_one

from package import module1 as module_two

import Tkinter as tk

自动载入模块

Python 解析器在标准模式下启动会自动的导入一个模块,例如 __bulitin__。我们可以通过 sys.modules 来查看已经被导入到当前环境下模块字典,key 为模块名,value 为模块路径。

import sys
sys.modules.keys()

模块导入的特性

  1. 加载模块时执行文件:在导入模块时,其顶格的代码(全局变量、类定义、函数定义、等)会被执行,所以不建议写太多的顶格代码,应尽量多的将顶格代码收入函数或类体内。

  2. 导入和加载模块是不一样的:一个模块可以被导入多次,但正常情况下只能被加载一次。当然除了手动加载之外。例如:你需要在一个模块中导入 5 个模块,其中之一为 sys 模块,然后另外的 4 个模块也需要导入 sys 模块。这种情况下,sys 模块只会被加载一次,但却会被导入 5 次。

  3. 导入到当前命名空间的变量:通过 from-import 导入到当前命名空间的模块变量是不需要通过句点标识符来调用的,直接就可以引用。 但是不建议通过 from-import 来直接导入模块变量,这样容易污染当前的命名空间,可能导致覆盖一个已经存在的具有相同名字的对象。

模块内建函数

import()

格式:

__import__(module_name[, globals[, locals[, fromlist]]])
  • globals:是全局命名空间的字典 globals()
  • locals:是局部命名空间的字典 locals()
  • fromlist:是 from-import 语句导入的变量名的列表 []

当我们执行 import 语句的时候,实际上是调用了 __import__() 内置函数。

import sys 
# 等效于
sys = __import__('sys')

globals()、locals()、reload()

  • globals():返回调用者的全局命名空间的字典。
  • locals():返回调用者的局部命名空间的字典。

NOTE:如果在全局命名空间调用上面两个内置函数,那么两者返回的字典是相同的。

  • reload():重新导入一个已经被导入了的模块。模块的顶格代码在导入的时候会执行且仅执行一次,再次导入的时候不会被执行,如果希望再次执行的话,可以使用 reload()。

NOTE:reload() 由两个使用限制:

  1. 模块必须全部被导入,不能重载通过 from-import 来导入模块属性的模块。
  2. 该模块必须是已经被导入了的,才能被重载。

Package

模块是代码的组织形式,包是模块的组织形式。

如果在一个目录中创建了 __init__.py 文件,那么 Python 解析器会将该目录标识为一个包 Package。

NOTE:我们一般在使用 import 语句的时候,建议导入到模块级别,不要导入模块属性,也不要导入包。

init.py

__init__.py 文件是包的标识文件,作为包的初始化模块,from-import 包下的模块会子包时,需要用到该初始化模块,所以现在如果一个包中没有该模块时,会触发 ImportWarning Error。

import package

如果我们不需要使用初始化模块 __init__.py 时,该模块的内容为空。如果我们 import package 时,实际上是 import packageName.__init__

NOTE:如果我们执行 from package import * 时,会将该包下的所有模块和属性导入,实际上这种做法是不科学的。所以在包的初始化模块文件中有一个特殊属性 __all____all__ 属性由一个模块名字组成的列表组成,这个列表中包含了所有在执行全导入时应该被导入的模块和属性的名字。

相对路径导入和绝对路径导入

在 import 时,有 “相对路径导入” 和 “绝对路径导入” 这 2 种方式。

  • 绝对导入import p1.m1 或者 from p1 import m1 等。
  • 相对导入from . import m1 或者 from .. import m1 或者 from ..p1 import m1 或者 from .m1 import f1 等。

比较而言,绝对导入更为简便清晰,在开发应用进程时建议选用;而相对导入则可维护性强,但是容易出错,在开发脚本程序时,建议使用。

绝对导入

# 先将 main Module 加入 sys.path 的绝对路径列表中。这样 main Module 就可以作为顶级模块了
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 
from main_p.sub_p import m1

相对导入

相对导入,即:相对于 main Module 执行导入。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

范桂飓

文章对您有帮助就请一键三连:)

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值