首页 > 代码库 > Python 点滴 III

Python 点滴 III

【Python模块的角色】

代码重用

系统命名空间的划分

实现共享服务和数据

【import模块工作步骤】

在Python中,导入并非只是如C中#include一样:把一个文件插入另外一个文件.程序第一次导入时,会执行三个步骤.

1. 找到模块

2. 编译成位码(需要时)

3. 执行模块的代码来创建其所定义的对象

【模块搜索路径】

按执行的先后顺序

1. 程序的主目录

2. PYTHONPATH目录

3. 标准链接库目录

4. 任何.pth文件的内容

这四个组件组合起来就变成了sys.path

>>> import sys
>>> sys.path
[‘‘, ‘C:\\WINDOWS\\system32\\python27.zip‘, ‘C:\\Python27\\DLLs‘, ‘C:\\Python27\\lib‘, ‘C:\\Python27
\\lib\\plat-win‘, ‘C:\\Python27\\lib\\lib-tk‘, ‘C:\\Python27‘, ‘C:\\Python27\\lib\\site-packages‘, ‘
C:\\Python27\\lib\\site-packages\\wx-3.0-msw‘]

【模块可以载人的文件类型】

按先后顺序,包括

1. 源代码文件.py

2. 字节码文件.pyc

3. 目录(包导入)

4. 编译扩展模块,导入时使用动态连接(Linux的.so以及Cygwin和Win的.dll或.pyd)

5. 用C编写的编译后的内置模块,并通过静态链接到PYTHON

6. ZIP文件组件,导入时会自动解压缩

7. 内存内映像,对于frozen可执行文件

8. Java类,在Jython版本的Python中

9. .NET组件,在IronPython版本的Python中

【第三方工具:distutils】

对模块搜索路径设置的说明,主要是针对自己编写的用户定义的源代码。PYTHON的第三方扩展,通常使用标准链接库中的distutils工具来自动安装,所以不需要路径设置,就能使用它们的代码。


使用distutils的系统一般都附带setup.py脚本,执行这个脚本可以进行持续的安装。这个脚本会导入并使用distutils模块,将这种系统放在属于模块自动搜索路径的一部分目录内。 无论是WIN,LINUX系统,通常是在Lib/site-packages子目录中.

【模块名设置规则】

模块和包名都必须遵循PYTHON中变量名规则。

一般.py结尾

一般小写

字母或下划线开头,余部只能包括数字,下划线,字母。 

不能包括PYTHON预留字,比如说if.py,命名是可以的,但当成包则不能导入

包的目录不能包含平台特有语法,比如说不能有空格

【导入只发生一次】

模块会在第一次import或from时载入并执行,并且只在第一次如此。这是有意为之的结果,因为该操作开销较大。在默认的情况下,PYTHON只对每个文件的每个进程做一次操作,并生成.pyc文件,理论上说,如果不能让客户看到源代码,可以直接提供所有的.pyc文件即可。之后的导入操作都只会读取已加载模块的对象.

比如说simple.py模块

print ‘HELLO‘

spam = 1

>>> import simple
HELLO
>>> simple.spam      #获取simple模块的spam属性
1
>>> simple.spam = 2  #改变模块里面的属性
>>> import simple    #再次导入
>>> simple.spam      #变量不会再次被初始化
2

【import和from】

import,from如同def,都是可执行的语句,而不是编译期间的声明,而且它们可以嵌套在if测试中,出现在函数def中,直到执行程序时,PYTHON执行到这些语句。此外,就像def一样,import和from都是隐性的赋值语句。

import 将整个模块对象赋值给一个变量名

from   将一个或多个变量赋值给另外一个模块中同名的对象。

比如下面例子

#small.py

x = 1       

y = [1,2]

>>> from small import x,y
>>> x = 42
>>> y[0] = 42

x是不可变对象,y则是共享的可变对象。改变了y的拷贝,原small模块中该变量也会改变.
>>> import small
>>> small.x
1
>>> small.y
[42, 2]

如果模块还没有加载,两个语句就会去搜索、编译以及执行模块文件程序。主要差别在于,import会读取整个模块,所以必须进行定义后才能读取它的变量名;from将复制模块特定的变量名.

#mod1.py

def mod1(x):
    print x

变量mod1有两个目的

1. 识别要被载入的外部文件

2. 同时生成脚本中的变量,在文件加载后,用来引用模块对象

>>> import mod1                  #导入模块,生成变量
>>> mod1.printx(‘Hello,World!‘)  #获得该模块的属性printx
Hello,World!


from语句是直接把变量名拷贝到另一个作用域,所以直接在脚本中使用复制后的变量名,而无需导入模块

>>> from mod1 import printx
>>> printx(‘Hello,World!‘)
Hello,World!


from *会取得模块顶层所有赋了值的变量名的拷贝

【文件间变量名的修改】

#small.py

x = 1       

y = [1,2]


在交互会话模式下对x的值赋值运算,只会修改该作用域里面的x,而不是这个文件内的x。以from复制而来的变量名和其来源的文件之间并没有联系。所以为了实际修改另一个文件中的全局变量名,必须使用import

>>> from small import x,y
>>> x = 42                 #仅仅改变x本身

>>> import small
>>> small.x                #改变smal模块里面的x

【import和from的对等性】

from只是把变量名从一个模块拷贝到另外一个模块。并不会对模块本身进行赋值.

像下面两个语句,是等效的.

from module import name1,name2

==

import module

name1 = module.name1

name2 = module.name2

del module

【from语句潜在的陷阱】

1. from语句会破坏命名空间,如果使用from来导入对象,碰巧那些变量和作用域中的现有变量同名,变量就会被悄悄覆盖掉。使用简单的import语句则不存在这个问题,因为你一定要通过模块名才能获取其属性,module.attr 与 attr不会冲突.

2. from module import *这种形式可能会破坏命名空间,让变量名难以理解,尤其是在导入一个以上的文件时。

3. from语句和reload同时使用时,from语句有比较严重的问题,因为导入的变量名可能来自之前版本的对象.

所以一般建议:

简单的模块使用import module的方式,而不是from

即使用from,最好列出想要的变量,而不是使用from *

【何时使用import】

当必须使用两个不同模块内定义的相同变量名的变量时,才真的必须使用import,这种情况下不能使用from. 例如:

#M.py

def func(x,y):

    return x + y

#N.py

def func(x,y):

    return x * y

当使用这两个版本的变量名时,会发生覆盖。

#A.py

from M import func

from M import func   #f会重写来自M中的变量func

func()               #仅仅是回调N中的func

正确的方法应该是

#A.py

import M,N           #获取整个模块,而不是它们的名字

M.func()             #通过模块.属性使它们唯一

N.func()

【模块命令空间】

模块可以理解为变量名封装,也就是定义想让系统其余部分看见变量名的场所。模块通常对应于文件,而PYTHON会建立模块对象,以包含模块文件内所赋值的所有变量名。简而言之,模块就是命名空间--变量名建立所在的场所。而存在于模块之内的变量名就是模块对象的属性。

【命名空间如何生成】

在模块的顶层,也就是不在函数或类里面的每一个赋值了的变量名都会变成该模块的属性

模块语句会在首次导入时执行

顶层的赋值语句会创建模块属性

模块的命名空间通过属性__dict__或dir(M)获取

模块式一个独立的作用域

例如: 在一个名为module.py文件中,内容如下:

print ‘starting to load...‘

import sys
name = 42

def func(): pass
class kclass(): pass

print ‘done loading.‘

开始导入时,PYTHON会从头到尾执行其语句。有些语句会在模块命名空间内创建变量名(name,func,kclass),而其他语句会在导入时做些实际的工作,比如此例中的两个print语句会在导入时执行.

>>> import module
starting to load...
done loading.

模块加载完成后,它的作用域就变成模块对象的属性的命名空间,通过import来获得。然后结合模块名,来获得命名空间内的属性。

>>> module.sys
<module ‘sys‘ (built-in)>
>>> module.name
42
>>> module.func
<function func at 0x000000000267F7B8>
>>> module.kclass
<class module.kclass at 0x0000000002669DC8>

此处,sys、name、func、kclass都是在模块语句执行时赋值的,所以在导入后都变成了属性。注意sys属性:import语句把模块对象赋值给了变量名,而文件顶层对任意类型赋值了变量名,都会产生模块属性。内部模块命名空间作为字典对象进行存储。通过__dict__属性获取命名空间字典。当然也就有.keys()方法

>>> module.__dict__.keys()
[‘name‘, ‘__builtins__‘, ‘__file__‘, ‘__package__‘, ‘sys‘, ‘func‘, ‘__name__‘, ‘__doc__‘, ‘kclass‘]

__file__ 被导入模块文件

>>> module.__file__
‘module.py‘              #如果要阅读、修改源码,非常有用

__name__ 被导入模块名    

>>> module.__name__      
‘module‘

【模块重载】

1. 导入只会在流程中第一次导入时,加载和执行该模块的代码

2. 之后导入只会使用已加载的模块对象,而不会重载或重新执行文件的代码

3. reload函数会强制使已加载的模块的代码重新载入并重新执行。此文件中新的代码的赋值语句会在适当的地方修改现有的模块对象。


为什么要重载模块? 

reload函数可以修改程序的一部分,而无须停止整个程序。因此,利用reload,可以立即看到组件的修改效果。可缩短开发流程。Python是解释性的,避免了类C语言程序执行时所需的编译/链接步骤:在执行程序导入时,模块会动态加载。重载进一步提供了性能优势,可以修改执行中的程序的一部分,而不需要终止。NOTE:reload当前只能用在PYTHON编写的模块;用C编写的编译后的扩展模块也可执行中动态加载,但无法重载。


reload与import和from不同的是:

1. reload是PYTHON中的内置函数,而不是语句,reload(module_name)

2. 传给reload的是已经存在的模块对象,而不是变量名

reload希望获得是对象,所以在重载之前,模块一定是已经预先成功导入的

【reload做了什么】

当调用reload时,Python会重读模块文件的源代码,重新执行其顶层语句。最重要的是:

1. reload不会删除并重建模块对象

2. reload只会在某些地方修改模块对象

具体细节如下:

1. reload会在模块当前命名空间内执行模块的新代码

   >>>重新执行模块文件的代码会覆盖其现有的命名空间,并非进行删除而重建

2. 文件中顶层赋值语句会使得变量名换成新值

   >>>重新执行的def语句会因重新赋值函数变量名而取代模块命名空间内该函数之前的版本

3. 重载会影响所有使用import读取了模块的客户端

   >>>因为需要通过点号运算取出属性,重载后,在模块对象中变成了新值

4. 重载只会对以后使用from的客户端造成影响

   >>>之前使用from来读取属性的客户端不会受到重载的影响

【模块重载用途】

1. 可以在交互式提示符号下重载模块

2. 可以在较大系统中也有用处,在重启整个应用程序的代价太大时尤其如此。

   >>>比如,必须在启动时通过网络连接服务器的系统,就是动态重载的重要场景

3. 可以在GUI中使用

   >>>组件的回调行为可以再GUI保持活动状态下进行修改

4. 当PYTHON作为C/C++的嵌入式语言时,也有用处

   >>>C/C++程序可以请求重载其所执行的PYTHON代码而无须停止。

5. 重载使程序能够提供高度动态的接口.

   >>>PYTHON通常作为较大系统的定制语言,用户可以在系统运作时通过编写PYTHON程序定制产品,而不需要重新编译整个产品

【包】

除了可以导入模块名外,还可以导入目录路径。Python代码的目录被称为包。包导入是把计算机上的目录变成另一个PYTHON命名空间,而属性则对应于目录中所包含的子目录和模块文件。包提供的层次,对于组织大型系统很有用,而且可以简化模块搜索路径的设置,特别是可以避免模块重名的情况。

【包导入】

两种形式:

import dir1.dir2.mod          #只能以点号来分隔变量

或者

from dir1.dir2.mod import x  

其中dir2是dir1的子目录,dir1必须是dir0的子目录,就当前程序执行的子目录

dir0\dir1\dir2\mod.py

NOTE:只能以点号来分隔,而不能用目录来分隔,比如说

import C;\dir0\dir1\dir2\mod.py

包的存在可以作为组织工具,简化模块的搜索路径,而且可以解决模糊性,还可以大幅简化PYTHONPATH和.pth文件搜索路径设置

【__init__.py包文件】

如果选择使用包导入,必须遵循一条约束:包导入语句的路径内的每个目录内都必须有__init__.py这个文件,否则导入包会失败。像dir0\dir1\dir2\mod.py这样一个目录对应的import语句

import dir1.dir2.mod

必须遵循下列规则:

. dir1和dir2中都必须包含有一个__init__.py文件

. dir0是容器,不需要__init__.py文件;有的话,也会被忽略

. dir0必须列在模块搜索路径上,也就是此目录必须是主目录或者列在PYTHONPATH之中

目录结构如下:

dir0 \

    dir1\

        __init__.py

        dir2\

            __init__.py

            mod.py


__init__.py 其实就是PYTHON程序的一种声明,可以为空。

存在的目的:

. 可以防止有相同名称的目录存在于模块的搜索路径中,如果没有这层保护,PYTHON可能会挑选出和程序无关的目录,只是因为有一个同名的目录刚好出现在搜索路径上位置前的目录内。


更通常的用途: __init__.py文件扮演了包初始化、替目录产生模块命名空间以及使用目录导入时实现from * 行为的角色。

包初始化:

   >>>Python首次导入某个目录时,会自动执行该目录下的__init__.py文件中的所有程序代码。因此,这类文件应该放在包内所需要初始化的场所。比如,可以用此文件来创建所需要的数据库文件,连接数据库

模块命名空间初始化:

   >>>在包导入的模型中,脚本内的目录路径,在导入后会变成真实的嵌套对象路径。如import dir1.dir2.mod 导入后,表达式dir1.dir2会运行,并返回一个模块对象,此对象的命名空间包含了dir2的__init__.py文件所赋值的所有变量名。这类文件为目录所创建的模块对象提供了命名空间.

from *语句的行为:

   >>>可以在__init__.py文件内使用__all__列表来定义目录以from *语句导入时,需要导入的内容.

【包导入例子】

#File: dir1\__init__.py

print ‘dir1 init...‘

x = 1


#File: dir1\dir2\__init__.py

print ‘dir2 init...‘

y = 2


#File: dir1\dir2\mod.py

print ‘in mod.py‘

z = 3

接下来就可以import和reload

>>> import dir1.dir2.mod
dir1 init...
dir2 init...
>>> reload(dir1)
dir1 init...
<module ‘dir1‘ from ‘dir1\__init__.pyc‘>
>>> reload(dir1.dir2)
dir2 init...
<module ‘dir1.dir2‘ from ‘dir1\dir2\__init__.pyc‘>

对象之间的嵌套

>>> dir1
<module ‘dir1‘ from ‘dir1\__init__.pyc‘>
>>> dir1.dir2
<module ‘dir1.dir2‘ from ‘dir1\dir2\__init__.pyc‘>
>>> dir1.dir2.mod
<module ‘dir1.dir2.mod‘ from ‘dir1\dir2\mod.pyc‘>

属性间调用

>>> dir1.x
1
>>> dir1.dir2.mod.z
3

简单起见:

>>> import dir1.dir2.mod as mod
>>> mod.z
3

【_X和__all__】

单下划线._X,私有变量,可以防止客户端使用from *导入,将变量名复制出去。

__all__可以达到同样的隐藏效果,可以再模块的顶层将要隐藏的变量名赋给__all__,from *会将导出列表里面的变量名

__all__=[‘coding‘,‘encoding‘]  #那么只会导出coding,encoding变量

两者区别:

__all__只对from * 有效,非私有变量

_X  真正的私有变量

【__name__和__main__】

每个模块都有个名为__name__的内置属性,PYTHON会自动设置该属性。

. 如果文件以顶层程序文件执行, 启动时,__name__就会被设置为字符串__main__

. 如果文件被导入,__name__就会被改设成模块名

#File: runme.py

def tester():

    print "This is name attr testing..."


if __name__==‘__main__‘:     #只有运行python runme.py这种方式时才执行

    tester()                 #导入时不执行

所以可以用__name__做测试,将要测试的函数,类放在__main__里面。比如说下面比较大小函数:

#File: comp.py

def minmax(test,*args):
    res = args[0]
    for arg in args[1:]:
        if test(arg,res):
            res = arg
    return res

def less(x,y): return x < y
def more(x,y): return x > y

print minmax(less,4,3,1,2,6,5)
print minmax(more,4,3,1,2,6,5)

如果是直接导入的话,测试部分会直接运行,在用户界面就不太友好.

>>> import comp
1
6

如果改成下面的话,

print ‘I am:‘, __name__


def minmax(test,*args):
    res = args[0]
    for arg in args[1:]:
        if test(arg,res):
            res = arg
    return res


def less(x,y): return x < y
def more(x,y): return x > y


if __name__==‘__main__‘:
    print minmax(less,4,3,1,2,6,5)
    print minmax(more,4,3,1,2,6,5)

就不会直接运行测试部分代码

>>> import comp
I am: comp
>>> comp.minmax(comp.less,4,3,1,5,6)
1

无论是否用于测试,结果都是让代码有两种不同的角色:

. 作为工具的库模块

. 作为可执行的程序

【修改模块搜索路径】

PYTHON程序本身要修改搜索路径,只要改变sys.path的内置列表,即改变sys模块的path属性.

>>> import sys
>>> sys.path
[...‘C:\\Python27‘] 
>>> sys.path.append(r‘D:\python‘)  #添加模块搜索路径, r的运用或D:\\python
>>> sys.path
[...‘C:\\Python27‘,‘D:\\python‘]

>>>import string                   #导入另外一个模块

NOTE:

1. 这只是临时生效,要永久生效的话,还是得改动PYTHONPATH或.pth文件

2. 注意如果是r‘d:\python‘等效于d:\\python,转译符的这种

3. 注意如果设置不当的话,会删除有用的搜索路径,导致其他模块不可用,如下:

>>> import sys
>>> sys.path=[‘d:\\temp‘]
>>> sys.path.append(r‘d:\python‘)

>>> sys.path                   #只有这两个搜索路径

[‘d:\\temp‘, ‘d:\\python‘]
>>> import string              #所有string模块导入不了
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: No module named string

用途: 运用此技巧,可以在PYTHON程序中动态配置搜索路径

【import as扩展】

import long_module_name as name

等价于

import long_module_name

name = long_module_name

del long_module_name


另外一种形式:

from module import long_name as name

作用:

1. 替长变量名提供简短的同义词,方便输入

2. 如果存在同名的变量的话,用as可以换个,避免变量名冲突

【模块属性几中调用方式】

>>> import math
>>> math.__name__                 #模块.属性
‘math‘
>>> math.__dict__[‘__name__‘]     #字典,取key
‘math‘
>>> sys.modules[‘math‘].__name__  #sys.modules,所有模块的列表
‘math‘
>>> getattr(math,‘__name__‘)      #内置函数获得模块的属性
‘math‘




Python 点滴 III