Python回顾与整理10:模块

0.说明

        模块是用来组织Python代码方法的方法,而包则是用来组织模块的,充分利用好包和模块将有利于开发出结构清晰的大型程序。


1.什么是模块

        所谓模块,其实就是一个包含了特定功能代码的.py文件,在这个.py文件中,主要有如下的代码类型:

  • 包含数据成员和方法的类
  • 一组相关但彼此独立的操作函数
  • 全局变量

        使用import语句就可以导入一个模块中的相关属性。


2.模块和文件

        模块是按照逻辑上来组织Python代码的方法,而体现在物理层面上,它就是一个文件,因此,一个文件被看作是一个独立模块,一个模块也可以被看作是一个文件。模块的文件名就是模块的名字加上扩展名.py。

        与其他可以导入类的语言不同,在Python中导入的是模块或模块属性。

(1)模块名称空间

        名称空间是Python非常重要的一个概念,所谓名称空间,其实指的是一个名称到对象的关系映射集合。可以因为每个模块都定义了它自己的唯一的名称空间,所以不同模块间不会出现名称交叉现象,通过句点属性的访问方式,即使两个模块里有相同名称的变量,由于模块名称的不同,也不会发生名称冲突。

(2)搜索路径和路径搜索

        模块的导入(使用import语句)需要一个叫做“路径搜索”的过程,即在文件系统“预定义区域”中查找要导入的模块文件,而这些预定义区域其实是Python搜索路径的集合,这里需要注意下面两个概念:

  • 路径搜索:指查找某个文件的操作,是动词
  • 搜索路径:需要查找的一组目录,是名词

        如果模块名称不在搜索路径中,就会触发ImportError异常:


1

2

3

4

>>> import mymodules

Traceback (most recent call last):

  File "<stdin>", line 1in <module>

ImportError: No module named mymodules

        而默认搜索路径是在编译或安装时指定的,可以在两个地方修改:

  • 设置环境变量PYTHONPATH
  • 在sys.path中添加搜索路径

        启动Python解释器后,搜索路径会被保存在sys模块的sys.path变量中:


1

2

3

>>> import sys

>>> sys.path

['', '/usr/local/lib/python2.7/dist-packages/pip-8.0.2-py2.7.egg', '/usr/local/lib/python2.7/dist-packages/setuptools-3.3-py2.7.egg', '/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', '/usr/lib/python2.7/dist-packages', '/usr/lib/python2.7/dist-packages/PILcompat', '/usr/lib/python2.7/dist-packages/gtk-2.0', '/usr/lib/python2.7/dist-packages/ubuntu-sso-client']

        返回的是一个列表,第一个元素表示的是当前目录。可以通过向这个列表添加元素(使用append或insert)来增加搜索路径:


1

2

3

4

5

6

>>> import my

Traceback (most recent call last):

  File "<stdin>", line 1in <module>

ImportError: No module named my

>>> sys.path.append('/home/xpleaf/test')

>>> import my

        如果有多个相同的模块名称,Python解释器会使用沿着搜索路径顺序找到的第一个模块。

        另外使用sys.modules可以找到当前导入了哪些模块和它们来自什么地方,如下:


1

2

>>> sys.modules

{'copy_reg': <module 'copy_reg' from '/usr/lib/python2.7/copy_reg.pyc'>, 'sre_compile': <module 'sre_compile' from '/usr/lib/python2.7/sre_compile.pyc'>,...}

        可以看到,与sys.path不同,sys.modules返回的是一个字典,其中key为模块的名称,键值为模块的路径。


3.名称空间

        名称空间是名称(标识符)到对象的映射,向名称空间添加名称的操作过程涉及绑定标识符到指定对象的操作,同时会给该对象的引用计数加1。主要有以下几种名称空间:

  • 内建名称空间:由__builtins__模块中的名字构成,Python解释器启动时自动加载
  • 全局名称空间:加载完内建名称空间后加载
  • 局部名称空间:执行一个函数时会产生该名称空间,函数执行结束后就会释放

        其中,关于__builtins__模块和__builtin__模块的区别,可以查看这篇博文:《Python中__builtin__和__builtins__的深入探讨》

        

(1)名称空间与变量作用域比较

        名称空间是纯粹意义上的名字和对象的映射关系,而作用域指出了从用户代码的哪些物理位置可以访问到这些名字。其关系如下:

        显然就很清晰了。

(2)名称查找、确定作用域、覆盖

        访问一个属性时,解释器必须要在三个名称空间中的一个找到它,首先是局部名称空间,如果没有找到,则查找全局名称空间,如果还是查找失败,就查找内建名称空间,最后还是失败了,就会引发NameError异常。

        显然上面的名称查找过程充分地体现了作用域覆盖的特性。

(3)无限制的名称空间

        可函数中,可以通过func.attribute给函数添加属性属性,从而创建了另外一个名称空间,而在面向对象编程中,也可以通过类似的方式给一个实例,一个实例就是一个名称空间:


1

2

3

4

5

6

7

8

9

10

>>> foo = test()

>>> foo.name = 'xpleaf'

>>> foo.loving = 'cl'

>>> 

>>> foo.name

'xpleaf'

>>> foo.loving

'cl'

>>> foo.__dict__

{'name''xpleaf''loving''cl'}


4.导入模块

(1)import语句

        使用import语句可以导入模块,建议以这样的顺序导入模块:

  • Python标准库模块
  • Python第三方模块
  • 应用程序自定义模块

        解释器执行import语句时,如果在搜索路径中找到该模块,就会进行加载,该过程遵循下面的原则:

  • 如果在一个模块的顶层导入,它的作用域是全局的
  • 如果在函数中导入一个模块,它的作用域是局部的
  • 如果模块是被第一次导入,它将加载并执行

        对于最后一点,可以验证如下:


1

2

3

>>> import test

Hello

>>> import test

        test模块中只有一个print语句,可以看到后面再次导入时并不执行该print语句。

(2)from-import语句

        可以使用from-import语句导入指定的模块属性,即把指定名称导入到当前作用域(全局名称空间或局部名称空间,取决于导入的位置)中,语法如下:


1

from module import name1[, name2[, ...nameN]]

        当然,可以多行导入:


1

2

from flask import render_template, redirect, flash, \

    url_for, request, current_app, jsonify

        也可以使用from-import-as:


1

from datetime import datetime as showtime


5.导入模块的特性

        当模块被导入时,会有如下的特性:

  • 载入时执行模块

        加载模块会导致这个模块被“执行”,也就是被导入模块的顶层代码将直接被执行,这通常包括全局变量以及类和函数的声明,如果有检查__name__的操作,那么它也会被执行。

        当然,应该把尽可能多的代码封装到函数,只把函数和模块定义放入模块的顶层是良好的模块编程习惯。

  • 导入(import)和加载(load)

        一个模块只被加载(执行)一次,无论它被导入多少次。前面已经有相应的例子。

  • 导入到当前名称空间

        即把模块的名称空间导入到当前作用域(全局名称空间或局部名称空间,取决于导入的位置),这样的话,如果当前作用域拥有相同名称的变量,就会被覆盖。

  • 关于__future__

        可以通过下面的导入语句使用Python的一些新特性:


1

from __future__ import new_feature

  • 警告框架

        需要使用到时再查看相关文档。

  • 从ZIP文件中导入模块
  • “新的”导入钩子

6.模块内建函数

        如下:

  • __import__()

        import语句实际上是调用了该函数,语法如下:


1

__import__(name, globals={}, locals={}, fromlist=[], level=-1)

        即可以这样来导入sys模块:


1

sys = __import__('sys')

  • globals()和locals()

        使用globals()和locals()可以分别输出包含全局名称空间和局部名称空间的一个字典,只是在全局名称空间下,globals()和locals()的输出是相等的,因为这时的局部名称空间就是全局名称空间。

        一个技巧是,可以通过这两个函数来调用全局或局部变量,在这开发中可能会用到,如下:


1

2

3

4

5

6

7

>>> def sayHi(name):

...     print 'Hello, ', name

... 

>>> globals()['sayHi']

<function sayHi at 0x7f56dc7bf140>

>>> globals()['sayHi']('xpleaf')

Hello,  xpleaf

  • reload()

        使用reload()可以重新加载一个已经导入的模块,对于前面只有print语句的test模块,测试如下:


1

2

3

4

5

6

>>> import test

Hello

>>> import test

>>> reload(test)

Hello

<module 'test' from 'test.pyc'>

        不过使用reload()时需要遵循下面的原则:

  1. 模块必须是全部导入,而不能是使用from-import
  2. reload()的参数是模块名字本身,而不是其字符串,即是reload(test)而不是reload('test')

7.包

        包是一个有层次的文件目录结构,它定义了一个由模块和子包组成的Python应用程序执行环境。

(1)目录结构

        有如下的包及其结构:

        在包外运行Python交互器,则下面的导入方式都是可以的:


1

2

3

4

5

6

7

8

9

10

11

import Phone.Mobile.Analog

Phone.Mobile.Analog.dial()

 

from Phone import Mobile

Mobile.Analog.dial('3245648')

 

from Phone.Mobile import Analog

Analog.dial()

 

from Phone.Mobile.Analog import dial

dial('3245648')

        只是上面的导入方式其实都是绝对导入。

(2)__init__.py

        只要是包,都必须包含__init__.py文件,否则只是一个普通的目录结构,而不是包,关于__init__.py,有如下特性:

  • 导入一个包,实际上是导入这个包的__init__.py文件(模块)

        对于上面的包结构,如果Phone/__init__.py的内容如下:


1

2

packageName = 'Phone'

print 'Package <Phone>'

        则导入Phone包时输出如下:


1

2

>>> import Phone

Package <Phone>

        此时只是把Phone加入到当前名称空间中:


1

2

>>> dir()

['Phone''__builtins__''__doc__''__name__''__package__']

        因为导入一个包实际上是导入这个包的__init__.py模块,所以,只要不在这个模块中的名称空间,都无法通过Phone来访问,即使已经导入了Phone:


1

2

3

4

5

6

7

8

>>> Phone.Mobile

Traceback (most recent call last):

  File "<stdin>", line 1in <module>

AttributeError: 'module' object has no attribute 'Mobile'

>>> Phone.Pager

Traceback (most recent call last):

  File "<stdin>", line 1in <module>

AttributeError: 'module' object has no attribute 'Pager'

        因为上面的__init__.py模块中并没有Mobile和Pager这两个变量,即这两个变量不在其名称空间中,因为__init__.py中定义了一个packageName变量,所以可以通过Phone来访问:


1

2

>>> Phone.packageName

'Phone'

  • 在__init__.py中可以先导入一些模块或子包,使得可以通过上层包访问子包

        上面的例子中,显然不能通过Phone来访问它的子包Mobile,现在可以在Phone/__init__.py中来先导入Phone的几个子包:


1

2

import Mobile

import Pager

        就可以通过Phone来访问了:


1

2

3

4

5

>>> import Phone

>>> Phone.Mobile

<module 'Phone.Mobile' from 'Phone/Mobile/__init__.pyc'>

>>> Phone.Pager

<module 'Phone.Pager' from 'Phone/Pager/__init__.pyc'>

        当然,如果想要访问子包下的模块,也是不行的,原因是一样的:


1

2

3

4

>>> Phone.Mobile.Digital

Traceback (most recent call last):

  File "<stdin>", line 1in <module>

AttributeError: 'module' object has no attribute 'Digital'

  • 使用__all__变量来导入所有子包

        如果需要使用from Package import *的方式来导入包下的所有模块,则需要定义__all__变量,实际上,该语句中的*表示的即是__all__列表中所包含的包名(或模块名)。

        Phone/__init__.py内容如下:


1

2

3

import Mobile

 

__all__ = ['Fax''Pager''Voicedta']

        使用from Package import *语句导入:


1

2

3

>>> from Phone import *

>>> dir()

['Fax''Pager''Voicedta''__builtins__''__doc__''__name__''__package__']

        可以发现只有Fax,Pager,Voicedta这三个子包被导入了,但是Mobile这个子包并没有被导入,那是因为它不在__all__变量所定义的列表中。

(3)绝对导入与相对导入

        使用下面的方式,都为绝对导入:


1

2

from Package import Module

import Package

        这时候,解释器会从sys.path定义的路径中去搜索需要导入的包或模块。当然,使用相对于当前路径的导入式的话有时候可以加快导入速度,相对导入可以使用下面的方式:


1

from .[Package/Module] import Modulce/Variable

        不过需要遵循下面的原则:

  • 必须是使用from import语句
  • from 关键字后面一定有句点标识.
  • 只适用在包中

        这三个原则都非常重要,尤其是最后一个,如果不清楚该原则的话,会导致错误使用相对导入从而带来不必要的各种纠结。

        关于这一点,可以参考《Python cookbook》的相关内容:《10.3 使用相对路径名导入包中子模块》


8.模块的其他特性

(1)自动载入的模块

        主要是__builtin__模块,它会正常地被载入,这和__builtins__模块相同。

(2)阻止属性导入

        如果不想让某个模块属性被"from module import *"导入,那么可以给不想导入的属性名称加上一个下划线(_)。不过如果导入了整个模块,这个隐藏数据的方法就不适用了。

        manage模块内容:


1

2

name = 'xpleaf'

_loving = 'cl'

        举例如下:


1

2

3

4

5

6

7

8

9

10

11

>>> from manage import *

>>> name

'xpleaf'

>>> _loving

Traceback (most recent call last):

  File "<stdin>", line 1in <module>

NameError: name '_loving' is not defined

>>> 

>>> import manage

>>> manage._loving

'cl'

(3)不区分大小的导入

        主要取决于操作系统的文件系统是否区分大小写。

(4)源代码编码

        在Python模块文件中,默认是使用ASCII编码,如果需要使用其他编码方法,比如Unicode编码,则可以在一个Python模块文件的开头这样写:


1

2

#!/usr/bin/env python

# -*- coding: UTF-8 -*-

        或者:


1

# coding: utf-8

(5)导入循环

        允许正确的导入循环,如果出现不能解决的导入循环问题,可以尝试在出现导入循环的两个模块中的一个模块中的import语句移到最后,或根据实际情况来进行解决。

(6)模块执行

        可以执行一个Python模块,参考后面的内容。


9.相关模块

        需要使用时参考相关文档即可。

时间: 2024-10-29 01:46:16

Python回顾与整理10:模块的相关文章

Python回顾与整理7:文件和输入输出

0.说明                  主要是下面的内容: 文件对象:内建函数.内建方法.属性 标准文件 文件系统:访问方法.文件执行 持久化存储 标准库中与文件有关的模块 1.文件对象         文件对象是用来访问文件的接口,而文件只是连续的字节序列,数据的传输经常会用到字节流,无论字节流是由单个字节还是大块数据组成. 2.文件内建函数(open()和file())         内建函数open()以及file()提供了初始化输入/输出(I/O)操作的通用接口,如果打开文件成功,

Python回顾与整理4:序列1—字符串

0.说明                  序列其实是Python的某几类数据类型的统称,如字符串,列表和元组,将它们统称为序列,是因为:它们的成员有序排列,并且可以通过下标偏移量访问到它的一个或者几个成员.         总结的思路为:先介绍适用于所有序列类型的操作符和内建函数,然后再分别对这几种序列类型进行介绍. 1.序列         序列类型都有相同的访问模式:它的每一个元素都可以通过指定一个偏移量的方式得到,多个元素通过切片操作的方式得到.而在Python序列中,偏移量的规则如下(

Python回顾与整理5:映像和集合类型

0.说明         依然是按照前面介绍的几种序列类型的思路来总结字映像类型和集合类型,即先做一个介绍,再讨论可用操作符.工厂函数.内建函数和方法. 1.映射类型:字典         映射类型被称做哈希表,而Python中的字典对象就是哈希类型,即它是按照这样的方式来存储数据:(哈希表的算法)是获取键,对键执行一个叫做哈希函数的操作,并根据计算的结果,选择在数据结构的某个地址中来存储对象的值.任何一个值存储的地址取决于它的键,正因为这种随意性,哈希表中的值是没有顺序的,所以Python中的

Python回顾与整理3:数字

0.说明               数字用的次数是否多主要是看需求,如果是做自动化运维平台开发,比如做一个自动监控的系统,那么你肯定需要收集一定量的数据,然后再对这些数据做一定的处理,那么这时候,你就一定需要用得上数字的.当然,我这里所说的要不要用数字,指的是,你是否需要对你的数据做一定的处理. 1.数字简介         数字是不可更改类型,也就是说变更数字的值会生成新的对象. (1)创建数值对象并用其赋值(数字对象) 1 2 >>>anInt = 1 >>>aC

Python回顾与整理4:序列2—列表与元组

1.列表         与字符串不同的是,列表不仅可以包含Python的标准类型,还可以包含不同类型的对象,包括用户自定义的对象.下面是一些列表的最基本的操作: 创建列表数据类型:由方括号([ ])定义,当然也可以用工厂方法list(iter)创建 访问列表的值:通过切片操作符([ ])和索引值或索引值范围访问 更新列表:可以在等号左边指定一个索引或者索引范围的方式来更新一个或几个元素,也可以用append()方法追加新元素到列表中 删除列表元素或列表本身:使用del L[index]的方法,

Python回顾与整理11:面向对象编程

0.说明         阅读一些优秀的Python源代码,会发现面向对象编程的思想无处不在,其实对于任何一门面向对象编程语言来说,面向对象编程都是极其重要的,因此,掌握好一门语言的面向对象编程,将有助于进行更高级的开发.(本文来自香飘叶子51cto博客<Python回顾与整理>系列博文专题) 1.引言 (1)类与实例 类         在Python中有新式类和经典类之分,无非就是有没有显式地继承一个父类: 1 2 3 4 5 6 7 # 新式类 class MyNewObjectType

Python回顾与整理8:错误和异常

0.说明         如果想写出用户体验高的代码,那么就需要考虑到在执行自己写的这段代码中在和用户交互的过程中可能会出现的问题,也就是说,需要对可能出现的异常进行处理,只有做好这些工作,才能写出用户体验好的代码. 1.什么是异常 错误         错误是语法(导致解释器无法解释)或逻辑(也就是代码质量问题)上的,在Python中,当检测到错误时,解释器会指出当前流无法继续执行下去,于是就出现了异常. 异常         程序出现了错误而在正常控制流以外采取的行为.         根据

Python回顾与整理1:Python基础

0.说明                  学习Python其实也有好一段时间了,之前也做了不少笔记,但是要真正把Python学得很扎实,没有对Python系统的了解是远远不够的,哪怕是最基础的知识点,所以决定好好地回顾整理.         当然,就以<Python核心编程>这本书为纲,希望可以把自己对Python的理解连成系统的一条线. 1.语句和语法 `#`:注释 `\`:换行,如果是闭合操作符如`( )`,`[ ]`,`{ }`等,可以不使用`\` `:`:分号将代码头和代码体分开 `

Python回顾与整理2:Python对象

0.说明                  说对象是面向对象编程语言最重要的一部分一点也不为过,没有了"对象",面向对象将无从谈起.Python也是如此,如果无法掌握对象,你很难有大的进步与提升. 1.Python对象 (1)对象特性         Python使用对象模型来存储数据,构造任何类型的值都是一个对象,所有的Python对象都拥有下面的三个特性: 身份:每个对象一唯一身份标识,可使用内建函数id()查看该值(可以认为这个值是该对象的内在地址) 类型:对象的类型决定了对象(