• 【Python】 关于import和package结构


    关于import语句

      python程序需要使用某个第三方模块的话要用import语句,其实就是把目标模块的内容加载到内存里。当然,在加载之前,python会按照一定的顺序寻找sys.path中的目录。sys.path中的第一项非常不起眼是'',但是千万要注意这是指当前工作目录。也就是说,如果当前工作目录下恰好有和你想import的模块名同名脚本的话,python是优先把那个脚本import进来,然后就停止搜索了。python中所有加载到内存中的模块都会记录到sys.modules中,这是一个模块名和模块文件的对应字典。当语句中出现import语句的时候,python首先会到这个字典中去找,如果找不到,就搜索sys.path给出的一些路径,寻找相关模块。找到之后就把它加入内存并载入sys.modules。需要注意的是import A.B的时候,解释器会先寻找A模块,然后再寻找A.B,A模块和A.B模块都会被记录到sys.module中去。

      在确认内存中已经加载了相关模块之后,python就会把这个模块名保存到local的命名空间中,如果有import...as..的话就以as为准。不过即使有as的时候,加载到sys.modules里面去的仍然是真正的模块名

      对于 from A import B这样的形式的话,python首先在sys.modules中找模块A.B,找到的话就获得了这个模块的对象而直接加载,没找到的话就新建一个空的对象<module xx>。再获取这个对象的__dict__(当然,一个空对象的__dict__肯定也是空的,所以python会先解析模块名.py来充实__dict__。),从中寻找相关子方法或子对象的信息,找不到则报错。

      *__dict__是一个类或者实例的固有属性,其内容是其所有成员属性和成员方法的名字以及内容的对应。

      比如下面这样一个示例:

    #在A.py中:
    from B import D
    class C:
        pass
    
    #在B.py中:
    from A import C
    class D:
        pass

      这样两个模块的话,先python A.py 来执行前者,首先python建立B.py的空对象,尝试解析B.py对象的__dict__,当解析到B的第一行时,又建立了A.py的空对象,然后为了得到A.py的__dict__又跑回去解析A.py。那么又碰到了A的第一句,但此时B.py的对象已经在内存中存在,但是它的__dict__仍然是空的没有被充实,所以最终会报ImportError:无法找到D

      如果是import一整个包而不是一个文件的话,那就是解析这个包里的__init__.py文件。

    ■  注意文件本身不能和module名重名

      今天写了一个email.py脚本里import了email模块,然后如果就在存放email.py的这个脚本的目录下运行它就报错了。这是因为python的工作目录是email.py存放的目录时,python优先搜索当前工作目录下的模块名(见上面的说明)

    ■  python包结构简单介绍

      在构建python包的时候,我们经常会看到__init__.py这个文件。含有__init__.py这个文件的目录可以认为是一个python的package,可以被其他文件导入的。一个简单的包就是下面这样子的:

    #######目录结构
    # ./
    # | test.py    这个是我们用来测试导入包的脚本,是__main__模块
    # | main/    测试中用到的包
    #      | __init__.py    这里面现在是空的,这个文件只为了表明main是个package
    #      | funcs.py    这里面定义一个函数func()供外部调用
    ########
    
    ####test.py中####
    import main
    import main.funcs
    from main.funcs import func
    #import main.funcs.func

      首先可以看出来,因为__init__.py的存在,main是一个包,所以在外界使用是可以直接import它的包名。以上前三个语句都是可以正确执行的,最后一个被注释的语句会报错,这说明在import语句中用圆点符来表示层级的时候最低只能表示到模块层(或者脚本这一层),而不能深入到变量或者函数层。如果想指定某个变量或者函数直接导入,那么就要用from xx import xx这样的形式了。在第两句那样的情况中,为了使用func,我们可以main.funcs.func来调用,所以说在普通语句中的原点符可以深入到变量或者函数层级中。但是在只有第一句的情况下,我们可能没有办法调用到func,因为此时写main.funcs.func是会报错的,中间那一节funcs并不会随着import main这个语句加载进来,具体可以在Import main之后dir(main)或者看下main.__dict__来看,是看不到funcs这个的。那么是不是说import main一无是处了呢?也不见得:

      我们再回头仔细看__init__.py这个文件,刚才假定了这个文件是空的,其实这里面也可以写很多东西,比如我在__init__.py中增加了一个名为var_p的变量。那么在test.py中要如何使用这个变量?很简单,就是import main然后再调用main.var_p,或者直接from main import var_p这个var_p除了是一个变量,也可以是一个函数,一个类等等。这就是说,在__init__.py这个文件中写的东西,被视为“包直属”的一些内容,所以通过包名main来直接调用。反过来说,__init__.py其实是在别人调用包名时指向的那个文件。比如此时我们再查看dir(main)就可以看到var_p的存在了。

      另外在这样的情景下还有一个小问题,加入__init__.py中定义了一个函数叫funcs,然后main包中又有一个模块叫funcs,引用这两个funcs好像都是用main.funcs。在import的时候我们可以区分开他们,因为作为函数的那个funcs是不能直接被import main.funcs的,而在from main import funcs的时候因为搜索命名空间的就近原则,导入的是函数funcs(前提是在此之前不能导入过main.funcs,否则会因为一些原因导致最终还是导入module的funcs,至于这个原因是什么,请看上面关于from import 和import 机制上的一个区别)。在使用的时候,一般情况下模块会是main.funcs来调用而函数会是funcs来调用,那如果在import模块的时候用了import main.funcs as funcs呢?这个的话就牵扯到命名空间的覆盖,如果先from main import funcs后然后再import main.funcs as funcs的话,模块funcs会覆盖函数funcs在命名空间中的地位,所以最终的funcs是模块funcs。再深入问一个,假如刚才两句语句反过来呢??从结果上来说没什么好说的,因为反过来即使没有as关键字的时候,funcs也是模块funcs。有了as关键字之后,尽管命名空间中会多出一个funcs来,但是加载到sys.modules里面去的仍然是main.funcs,所以from import 的时候依然会先找到模块的main.funcs,所以from import的结果依然是模块funcs。

      python的package之间还可以进行嵌套,比如可以有一个叫app的package,这意味着存在app/__init__.py这个文件,然后app下面可以有个子目录叫main,main里有个__init__.py,这样main就算是一个子package,但也是一个package。嵌套包含关系的包自然就需要用.来导入,比如上面说的就可以import app.main这样的感觉来导入。类似的,import 进来的app.main其实是代表了main/__init__.py这个文件,app.main.var可以访问这个init文件中定义的叫var的变量or函数or类等。

    ■  关于包内导入和relative import

      上面讲了一大堆比较复杂,究其复杂的原因,就是因为我们没有办法通过main.funcs.func来直接调用包中某个模块中的某个对象。其实聪明的同学可能能想到,之前我们在__init__.py里手动定义了一个funcs可以直接拿来用,那么可不可以把模块funcs也弄到__init__.py里面去。这样不就能直接用了。很自然,就会想到在__init__.py中写上 import funcs即可。这个就是包内导入了(__init__.py和funcs.py在同一个包内)。这是一个最为简单的包内导入的例子,如果想要用from import 的形式来import funcs这个模块的话,可以这么写:from . import funcs。这个“.”在这里,代表的就是当前目录的意思,而这种导入方法就是所谓的relative import 即相对导入了。

      和所有“相对”和“绝对”的东西一样,这个相对导入更加灵活。在from后面加一个点就是指当前目录,加上两个点就是从上级目录导入。如果在点后面直接接上某个名字那就是说从点的数量指向的那个目录下找到相应名字的模块,from这个模块去import一些东西。

      有时候,我们在自己自定义的包里里面可能碰巧有和内建模块重名的文件出现,此时在导入的时候解释器可能会闹不清该导入什么模块,此时可以用相对导入来明确指出我需要的是我自己定义的模块而不是内建的。(然而绝对导入时似乎也是这样的。。不知道为甚网上很多人都这么写,闹得不太清楚。。)

      需要注意的是,使用了相对导入的文件只能作为module来用,而不能作为__main__模块来直接运行,否则会报Attempted relative import in non-package错。这是因为相对导入时找路径是通过模块名__name__去找的,如果模块名不是脚本名而是__main__了那么自然是找不到了。

  • 相关阅读:
    如何将一棵树转化成二叉树
    雪碧图的使用
    CSS简介,引入方式,文字和文本样式设置
    表格Table和表单元素
    html 中< col>标签和< colgroup>标签的区别
    Emmet的HTML语法(敲代码的快捷方式)
    抖音风格字体效果
    几种有效减小电脑系统盘使用量的方法
    ubuntu 机器名称修改方法
    Ubuntu 为基于X应用程序增加启动项的正确做法
  • 原文地址:https://www.cnblogs.com/franknihao/p/6626010.html
Copyright © 2020-2023  润新知