• Python之模块与包(上)


    1、什么是模块

    定义:一个模块就是一个包含了python定义和声明的文件(文件名就是模块名字加上.py的后缀),模块可以被导入使用。

    先来定义个模块:

    #spam.py:spam 模块里有 print 的语句,当加载 spam 模块的时候,会解析出来执行
    print('from the spam.py')
    
    money=1000
    
    def read1():
        print('spam模块:',money)
    
    def read2():
        print('spam模块')
        read1()
    
    def change():
        global money
        money=0

    2、模块导入方法 import

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

    #模块可以包含可执行的语句和函数的定义,这些语句的目的是初始化模块,它们只在模块名第一次遇到导入import语句时才执行(import语句是可以在程序中的任意位置使用的,且针对同一个模块import多次,为了防止你重复导入,python的优化手段是:第一次导入后就将模块名加载到内存了,后续的import语句仅是对已经加载到内存中的模块对象增加了一次引用,不会重新执行模块内的语句),如下

    #test.py
    import spam #只在第一次导入时才执行spam.py内代码,此处的显式效果是只打印一次'from the spam.py',当然其他的顶级代码也都被执行了,只不过没有显示效果.
    import spam
    import spam
    import spam
    
    '''
    执行结果:
    from the spam.py
    '''
    test.py

    首次 import 做了三件事


    #1.为源文件(spam模块)创建新的名称空间,在spam中定义的函数和方法若是使用到了global时访问的就是这个名称空间。

    #2.在新创建的名称空间中执行模块中包含的代码,见初始导入import spam
    提示:导入模块时到底执行了什么?
    In fact function definitions are also ‘statements’ that are ‘executed’; the execution of a module-level function definition enters the function name in the module’s global symbol table.
    事实上函数定义也是“被执行”的语句,模块级别函数定义的执行将函数名放入模块全局名称空间表,用globals()可以查看
    有点绕是不是?简单一句话,就是把模块里面的函数放到当前文件里面,相当于在当前文件里面写了这么一个函数,并加载

    #3.创建名字spam来引用该命名空间
    这个名字和变量名没什么区别,都是‘第一类的’,且使用spam.名字的方式可以访问spam.py文件中定义的名字,spam.名字与test.py中的名字来自两个完全不同的地方。


    被导入模块有独立的名称空间

    每个模块都是一个独立的名称空间,定义在这个模块中的函数,把这个模块的名称空间当做全局名称空间,这样我们在编写自己的模块时,就不用担心我们定义在自己模块中全局变量会在被导入时,与使用者的全局变量冲突

    #test.py
    import spam 
    money=10
    print(spam.money)
    
    '''
    执行结果:
    from the spam.py
    1000
    '''
    
    
    
    #test.py
    import spam
    def read1():
        print('========')
    spam.read1()
    
    '''
    执行结果:
    from the spam.py
    执行流程:通过:spam->找:read1->打印:money 1000
    '''
    
    
    
    #test.py
    import spam
    money=1
    spam.change()
    print(money)
    
    '''
    执行结果:
    from the spam.py
    1
    '''
    View Code


    为模块名起别名

    为已经导入的模块起别名的方式对编写可扩展的代码很有用

    import spam as sm
    print(sm.money)

    例1:有两中sql模块mysql和oracle,根据用户的输入,选择不同的sql功能

    #mysql.py
    def sqlparse():
        print('from mysql sqlparse')
    #oracle.py
    def sqlparse():
        print('from oracle sqlparse')
    
    #test.py
    db_type=input('>>: ')
    if db_type == 'mysql':
        import mysql as db
    elif db_type == 'oracle':
        import oracle as db
    
    db.sqlparse()
    View Code

    例2:假设有两个模块xmlreader.py和csvreader.py,它们都定义了函数read_data(filename):用来从文件中读取一些数据,但采用不同的输入格式。可以编写代码来选择性地挑选读取模块

    if file_format == 'xml':
        import xmlreader as reader
    elif file_format == 'csv':
        import csvreader as reader
    data=reader.read_date(filename)
    View Code


    3、模块导入方法 from ... import

    from spam import read1,read2

    from...import 与 import 的对比

    #唯一的区别就是:使用from...import...则是将spam中的名字直接导入到当前的名称空间中,所以在当前名称空间中,直接使用名字就可以了、无需加前缀:spam.

    #from...import...的方式有好处也有坏处
    好处:使用起来方便了
    坏处:容易与当前执行文件中的名字冲突

    验证一:当前位置直接使用read1和read2就好了,执行时,仍然以spam.py文件全局名称空间

    #测试一:导入的函数read1,执行时仍然回到spam.py中寻找全局变量money
    #test.py
    from spam import read1
    money=1000
    read1()
    '''
    执行结果:
    from the spam.py
    通过:spam->执行:read1->打印:money 1000
    '''
    
    #测试二:导入的函数read2,执行时需要调用read1(),仍然回到spam.py中找read1()
    #test.py
    from spam import read2
    def read1():
        print('==========')
    read2()
    '''
    执行结果:
    from the spam.py
    spam->read2 calling read1
    spam->read1->money 1000
    '''
    View Code

    验证二:如果当前有重名read1或者read2,那么会有覆盖效果。

    #测试三:导入的函数read1,被当前位置定义的read1覆盖掉了
    #test.py
    from spam import read1
    def read1():
        print('==========')
    read1()
    '''
    执行结果:
    from the spam.py
    ==========
    '''
    View Code

    验证三:导入的方法在执行时,始终是以源文件为准的

    from spam import money,read1
    money=100 #将当前位置的名字money绑定到了100
    print(money) #打印当前的名字
    read1() #读取spam.py中的名字money,仍然为1000
    
    '''
    from the spam.py
    spam->read1->money 1000
    '''
    View Code


    from ... import * 调用模块的所有方法,尽量不要用,可能和自己定义的起冲突

    #大部分情况下我们的python程序不应该使用这种导入方式,因为*你不知道你导入什么名字,很有可能会覆盖掉你之前已经定义的名字,而且可读性极其的差。

    from spam import * # 将模块spam中所有不是以下划线(_)开头的名字都导入到当前名称空间
    print(money)
    print(read1)
    print(read2)
    print(change)
    
    '''
    执行结果:
    from the spam.py
    1000
    <function read1 at 0x1012e8158>
    <function read2 at 0x1012e81e0>
    <function change at 0x1012e8268>
    可以使用__all__来控制*(用来发布新版本),在spam.py中新增一行
    __all__=['money','read1'] #这样在另外一个文件中用from spam import *就这能导入列表中规定的两个名字
    View Code


    4、模块与脚本

    #编写好的一个python文件可以有两种用途:

    一:脚本,一个文件就是整个程序,用来被执行

    二:模块,文件中存放着一堆功能,用来被导入使用


    #python为我们内置了全局变量__name__,

    当文件被当做脚本执行时:__name__ 等于'__main__'

    当文件被当做模块导入时:__name__等于模块名


    #作用:用来控制.py文件在不同的应用场景下执行不同的逻辑

    if __name__ == '__main__':

    run() # 执行当前脚本里的逻辑

    #fib.py
    
    def fib(n):    # write Fibonacci series up to n
        a, b = 0, 1
        while b < n:
            print(b, end=' ')
            a, b = b, a+b
        print()
    
    def fib2(n):   # return Fibonacci series up to n
        result = []
        a, b = 0, 1
        while b < n:
            result.append(b)
            a, b = b, a+b
        return result
    
    if __name__ == "__main__":
        import sys
        fib(int(sys.argv[1]))
    
    
    #执行:python fib.py <arguments>
    python fib.py 50 #在命令行执行
    脚本例子


    5、模块搜索路径

    模块的查找顺序是:内存中已经加载的模块->内置模块->sys.path路径中包含的模块

    1、在第一次导入某个模块时(比如spam),会先检查该模块是否已经被加载到内存中(当前执行文件的名称空间对应的内存),如果有则直接引用

    ps:python解释器在启动时会自动加载一些模块到内存中,可以使用sys.modules查看

    2、如果没有,解释器则会查找同名的内建模块

    3、如果还没有找到就从sys.path给出的目录列表中依次从左到右寻找spam.py文件。

    #需要特别注意的是:我们自定义的模块名不应该与系统内置模块重名。比如:不要自定义一个叫 json.py 的模块

    #在初始化后,python程序可以修改sys.path,路径放到前面的优先于标准库被加载。

    >>> import sys
    
    >>> sys.path.append('/a/b/c/d')
    
    >>> sys.path.insert(0,'/x/y/z') #排在前的目录,优先被搜索

    注意:搜索时按照sys.path中从左到右的顺序查找,位于前的优先被查找,sys.path中还可能包含.zip归档文件和.egg文件,python会把.zip归档文件当成一个目录去处理。


    6、编译python文件

    为了提高加载模块的速度,强调强调强调:提高的是加载速度而绝非运行速度。

    python解释器会在__pycache__目录中下缓存每个模块编译后的版本,格式为:module.version.pyc。

    通常会包含python的版本号。例如,在CPython3.3版本下,spam.py模块会被缓存成__pycache__/spam.cpython-33.pyc。这种命名规范保证了编译后的结果多版本共存。

    Python检查源文件的修改时间与编译的版本进行对比,如果过期就需要重新编译。这是完全自动的过程。并且编译的模块是平台独立的,所以相同的库可以在不同的架构的系统之间共享,即pyc是一种跨平台的字节码,是由python虚拟机来执行的,但是pyc的内容跟python的版本相关,不同的版本编译后的pyc文件不同,2.5编译的pyc文件不能到3.5上执行,并且pyc文件是可以反编译的,因而它的出现仅仅是用来提升模块的加载速度的,不是用来加密的。

    .

  • 相关阅读:
    理解SSL、HTTPS原理中的对称加密与非对称加密
    lib下的Jar包在项目打包的时候提示不能找不到
    springboot2.0 最大上传文件大小遇到的错误Failed to bind properties under 'spring.servlet.multipart.max-file-size'
    Executors创建线程池的几种方式以及使用
    JAVA深入研究——Method的Invoke方法(转)
    git merge 与 git rebase的区别
    git rebase
    git fetch , git pull
    nginx搭建(centos7)
    idea过期激活
  • 原文地址:https://www.cnblogs.com/tootooman/p/9070743.html
Copyright © 2020-2023  润新知