zoukankan      html  css  js  c++  java
  • 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的功能。

  • 相关阅读:
    WPF控件操作之改变父控件之TabControl示例
    WPF里面的DockPanel的Fill去哪了,如何填满整个空间
    [原创]winform自定义控件之类属性-多重属性-可折叠属性
    WinForm之DataBinding
    WinForm自定义控件之DefaultValue的误解
    code snippet:依赖属性propa的小技巧
    【原创】WinForm中实现单独Time控件的方式
    node.js安装本地模块遇到的目录锁定问题【新手问题】
    《程序员思维修炼》之德雷福斯模型
    IOptions、IOptionsMonitor以及IOptionsSnapshot
  • 原文地址:https://www.cnblogs.com/f1194361820/p/8641288.html
Copyright © 2011-2022 走看看