Python : Module

          在Python中,一个.py文件代表一个Module。在Module中可以是任何的符合Python文件格式的Python脚本。了解Module导入机制大有用处。

 

1 Module组成

         一个.py文件就是一个module。Module中包括attribute, function等。 这里说的attribute其实是module的global variable。

在一个ModuleTests.py文件中:

#!python
#-*- coding: utf-8 -*-

"""
全局变量
"""

# hello doc
global moduleName
moduleName = __name__
a = 1

def printModuleName():
    print(a+1)
    print(__name__)
    print(moduleName)

'''
if __name__ == '__main__' : 
    print('current module name is "' + __name__+'"')
'''


printModuleName()
print(a)
print(dir())

import __builtin__
print(__builtin__ == __builtins__)
print(__doc__)
print(__file__)
print(__name__)
print(__package__)
__name__ = 'hello'
print(__name__)
View Code

         除了你自己定义的那些全局变量和函数外,每一个module还有一些内置的全局变量。在这个module就包括了三个attribute:a,moduleName,printModuleName。如果该模块被导入到另一个模块,在另个一模块中,就可以通过某种方式来访问这三个attribute。

 

1.1 Module 内置全局变量

         每一个模块,都会有一些默认的attribute(全局变量)。dir()函数 是python中的一个顶级函数,勇于查看模块内容。例如上面的例子中,使用dir()查看结果是:

 ['__builtins__', '__doc__', '__file__', '__name__', '__package__', 'a', 'moduleName', 'printModuleName']。其中a, moduleName, printModuleName 是由用户自定义的。其他的全是内置的。

        1)__name__ :模块的名称。例如上面的ModuleTests.py,模块的名称默认就是ModuleTests。在运行时,如果一个module是程序入口,那么__name__就是”__main__”。它是最常用的。

        2)__builtins__:在Python中有一个内置的module,叫做:__builtin__,它是一个Python的模块。而任何一个Python的模块都有一个__builtins__全局变量,它就是内置模块__builtin__的引用。可以通过如下代码测试:

import __builtin__
print(__builtin__ == __builtins__)

        // 测试结果是True

在Python代码里,不需要我们导入就能直接使用的函数,类等,都是在这个内置模块里的。例如:range(),bytes(),dir()。

        3)__doc__:module的文档说明。即便是Python的初学者都知道Python中的多行注释是用三对单引号或者双引号包含的。网上有人说__doc__其实就是注释,这句话呢说的太随意容易给人误解。经过测试,模块的__doc__应该是:文件头之后代码(包含import)之前 第一个 多行注释。 方法的__doc__是方法前的那个注释。

在交换模式下,我们可以直接使用__doc__来查看方法的说明的。例如查看string.split方法的说明:str.split.__doc__就可以了。

 

        4)__file__:当前module所在的文件的路径。

        5)__package__:当前module所在的包名。如果没有,为None。

 

1.2 dir()的妙用 

        dir()是一个内置函数,用于查找指定的module中包括哪些attribute和method (或者function)。如果不指定参数,默认是当前module。上面说了range,dir,bytes等都是在内置模块里的,那么到底是不是呢?

        dir(__builtins__)就可看到了:

 

 

2 Module导入

2.1 导入及其使用

         一个Module可以导入(import)到其他的Python脚本中使用。导入方式有多种:

        1)import module1

        2)import module1 as m1

        3)from module1 import xxx

        4)from module1 import xxx as yyy

        从包(package)导入,也分为类似的三种:

        1)import p1.p2.p3.module1 

        2)import p1.p2.p3.module1 as m1

        3)from p1.p2.p3.module1 import xxx

        4)from p1.p2.p3.module1 import xxx as yyy

        假设module1有两个attribue: a1,a2, 两个function: f1,f2下面来说明这几种导入方式的区别:

         方式一是导入整个module1, 并将赋值给一个变量module1,来供使用。使用时,可以使用module1.a1, module1.a2, module1.f1(params), module.f2(params)

         方式二是在方式一的基础上,重命名为m1,也就是说使用时得使用: m1.a1, m1.a2, m1.f1, m1.f2。

         方式三是导入模块的部分内容(导入一个或者一些attribute或者function) 。例如 from module1 import a1,导入完成后,在当前的模块中创建了一个 a1的变量。调用是直接调用a1即可。

         方式四对于导入一个attribute或者function时,可以重命名。例如 from module1 import a1 as msg,那么导入完毕,就是在当前的模块中创建了一个msg的变量,指向了module1.a1。 我们在调用时,只能通过msg来调用。

  

2.2 一次加载多次导入

 

       对于上面的4种导入方式,不论哪一种,都有两个阶段:1)找到module对象,2)按需分配给变量。

       模块本身就是为了复用的。在一个大的项目中,一些基础的、公共的模块通常会被大量使用,也就是说会被很多的module导入使用。我们也知道,module是放在py文件中的。如个一个module被大量导入时,难道要每一次导入,都去磁盘上找一py文件吗?

       显然不能这样设计,如果真的这样设计,程序的性能将是极差的了。

       对于同样的问题,Java中的做法是,使用ClassLoader加载类,并采用父加载器委托机制。尽可能的保证,同一个ClassLoader下,在多次引用一个类时,都是同一个。我们可以将该方式称为一次加载,多地使用。

       Python的设计者,也考虑到这个问题。也采用了类似方案,被我称为一次加载,多次导入。我们假设它有一个Module Loader的存在,在首次加载(其实是首次import)时,执行流程如下:

       1)由Module Loader从检索路径下找出相应的模块

       2)编译或者找到合适的字节码文件(.pyc结尾)

       3)解释执行要导入的Module,并放入缓存。

       4)将导入的Module对象(或者其属性)分配给当前Module下的变量。

      随后整个程序中再有执行import该moudle时,只需要从缓存中拿到该module,然后执行4)。

 

       此外,对于过程2)有这样4种情况:

       A: 若.py与.pyc都存在:会对.py文件的最后修改时间与.pyc文件的最后修改时间比较。执行时间靠后的那个。

       B: 若.py与.pyc都不存在,继续找,如果最终都没有找到,出错。

       C: 若.py存在,.pyc不存在:编译.py为.pyc。

       D:若.py不存在,.pyc存在,直接执行.pyc。

 

       再者还要说明2点:

       1)一次加载,多次导入的机制,在Python程序包中提供的交互式命令行里使用import是不管用的。在交互式下,一次加载只能用于一次导入。

       2)一般main py是不会被编译成pyc的,一个模块要想被编译成pyc,需要import到其他模块才行。

 

2.3 搜索路径

       依据Java编程经验来看,通常程序会将文件放在不同的地方。Python必然也不例外。Python的搜索顺序为:

       1)  已加载模块的缓存

       2)  内置模块

       3)  sys.path

       其中sys.path包含以下几部分:

       1)入口程序的目录

       2)系统环境变量PYTHONPATH代表的目录

       3)标准Python库目录

       4)任何.pth文件的内容(如果存在的话)

      

       下面使用命令看一下sys.path的目录有哪些:

['',
'C:\\windows\\SYSTEM32\\python27.zip',
'D:\\Program Files\\Python\\Python27\\DLLs',
'D:\\Program Files\\Python\\Python27\\lib',
'D:\\Program Files\\Python\\Python27\\lib\\plat-win',
'D:\\Program Files\\Python\\Python27\\lib\\lib-tk',
'D:\\Program Files\\Python\\Python27',
'D:\\Program Files\\Python\\Python27\\lib\\site-packages']

        如果要加载的module不在上述目录下,可以通过3钟手段:

        1)  配置环境变量PYTHONPATH,配置是与环境变量PATH的风格一样。

        2)  程序动态修改sys.path

        3)  放到site-packages目录下。

 

2.4 reload()

        有些情况下,我们需要在程序运行是对程序代码做修改。例如我们需要监控某一方法执行快慢,是否存在性能问题时。我们需要在function的开始、结束部分记录一个startTime,endTime,依此来判定执行性能时。像这样的场景下,因为Module的加载一次,多长调用的机制,我们修改完代码,也不会生效。此时就需要一种机制来重新加载module,以达到期望效果。reload() 就可以解决这个问题。

        reload(module) 是一个函数,参数是一个module对象。执行reload,就会等于再一次进行加载。

 

3 Package

 

3.1 __init__.py

        每一个package下必须有一个__init__.py文件,该文件用于表明当前目录可以作为一个package。

        __init__.py 也是一个python,当首次加载相应的package时,会执行__init__.py。

        __init__.py文件可以什么也没有,也可以指定__all__或(和)__path。

 

3.2 __all__

       __all__的值是一个列表,用于当程序中使用 from pkg1.pkg2.pkg3 import * 时。

       就拿Python_HOME/Lib/下的json包来做实验,由于该文件比较大,我就写出主要部分:

__version__ = '2.0.9'
__all__ = [
    'dump', 'dumps', 'load', 'loads',
    'JSONDecoder', 'JSONEncoder',
]
__author__ = 'Bob Ippolito <bob@redivi.com>'

from .decoder import JSONDecoder
from .encoder import JSONEncoder

def dump(params):
    pass

def dumps(params):
    pass

def load(params):
    pass

def loads(params):
    pass

       目录结构如下: 

 

       当程序中使用 from json import * 引起json包首次加载时,执行过程如下:

       1)找到json目录,执行__init__.py 执行完毕后:包下会暴漏出:load,loads,dump.dumps, JSONDecoder, JSONEncoder

       2)查找* ,即从__all__找出要导出的变量。

 

       当程序使用Import json.encoder引起json包首次加载时,执行过程如下:

       1)找到json目录,执行__init__.py 执行完毕后:包下会暴漏出:load,loads,dump.dumps, JSONDecoder, JSONEncoder (以供from json import * 使用)

       2)导入json包到一个变量里(此后程序可以直接使用json.encoder, json.decoder,json.scanner)

 

       上述结论,来源于下面的测试用例:

#!python
#-*- coding: utf-8 -*-

"""
Package Import Test
"""

#from json import *
from json import encoder
import json

print(json.encoder == encoder)
print(json.decoder is None)
print(json.scanner is None)
print(dir())

 

3.3 __path__ 

       该变量用于配置包下的搜索位置。例如:

       在Utils下增加2个目录Linux和Windows, 并各有一个echo.py文件, 目录如下 

 Sound/Utils/  
    |-- Linux        目录下没有__init__.py文件, 不是包, 只是一个普通目录  
    |   `-- echo.py  
    |-- Windows      目录下没有__init__.py文件, 不是包, 只是一个普通目录  
    |   `-- echo.py  
    |-- __init__.py  
    |-- echo.py  
    |-- reverse.py  
    `-- surround.py  

      如果__init__.py是空的,当使用import Sound.Utils.echo导入echo时,会导入的是Sound/Utils/echo.py。   

      接下来我将__init__.py做如下修改:

import sys 
import os 

print "Sound.Utils.__init__.__path__ before change:", __path__ 

dirname = __path__[0] 
if sys.platform[0:5] == 'linux': 
        __path__.insert( 0, os.path.join(dirname, 'Linux') ) 
else: 
        __path__.insert( 0, os.path.join(dirname, 'Windows') ) 
print "Sound.Utils.__init__.__path__ AFTER change:", __path__

      在Linux上执行import Sound.Utils.echo,那么搜索路径就会变成了: 'Sound/Utils/Linux', 'Sound/Utils'。以此来达到自动化的按需加载响应的module的功能。

posted @ 2018-03-24 20:22  乐享程序员  阅读(5356)  评论(0编辑  收藏  举报