import 的作用

import基本上是python最基本的元素之一,只要你需要协助,几乎就得import一个模块来达到目的,一句话概括import的作用就是:通过import,你可以获得使用其他模块的代码的权限。平时基本上只是使用,但是import其实是一个系统,里面的学问大着呢

import 的机制

import的语法,根据官网的文档,如下

import_stmt     ::=  "import" module ["as" name] ( "," module ["as" name] )*
                     | "from" relative_module "import" identifier ["as" name]
                     ( "," identifier ["as" name] )*
                     | "from" relative_module "import" "(" identifier ["as" name]
                     ( "," identifier ["as" name] )* [","] ")"
                     | "from" module "import" "*"
module          ::=  (identifier ".")* identifier
relative_module ::=  "."* module | "."+
name            ::=  identifier

上面的说明类似正则表达式,只是体现了语法的格式,并没有解释module和relative_module是啥,我们暂时先忽略这里的含义,根据上面的说明,以下语法都是合法的:

  1. import module_a
    这里的module_a是啥呢,module_a可以是一个文件夹,即使这个文件夹是空的,或者是一个py文件,即使文件是空的,如果是其他文件呢,假设有个文件为module_c.txt,那我们import module_c会发生什么呢,首先我们先在cmd命令行窗口 cd 到该文件所在的文件夹,然后输入python(前提是要安装python),在python命令行中输入import module_c,结果如下
>>> import module_c
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
ImportError: No module named 'module_c'

可见import xxx,这个xxx,这个xxx称为模块(module),这个module包含什么呢,按照官方的文档介绍:A Python module which can contain submodules or recursively, subpackages,意思大概是一个python模块可以包含子模块也可以包含子包,子模块可以包含子子模块,以此类推。所以xxx必须是文件夹或者py后缀的文件,如果是文件夹,我们称这个为包,package,按照官方的文档,package分为两种,一种是regular package,另一种是namespace package。
1) regular package
正规的包包含有 __init__.py 文件
2)namespace package
命名空间包只包含子包,没有__init__.py 文件
两者有什么区别呢,下面我们分别创造这两种包,看一下import之后,他们有什么不一样,首先是regular package,文件结构如

– regular_package
|—-__init__.py

cmd下cd 到regular_package的上一级目录,打开python,执行以下操作

>>> import regular_package
>>> import sys
>>> sys.modules['regular_package']
<module 'regular_package'
from'F:\\yjm\\py_learn\\import_ex\\regular_package\\__init__.py'>
>>>
由此可见import导入的是regular_package下的__init__.py,并标明路径,下面请看namespace package,创建一个名为namespace_package的空文件夹,然后做如下操作
>>> import namespace_package
>>> import sys
>>> sys.modules['namespace_package']
<module 'namespace_package' (namespace)>
>>>
由此可见,没有__init__.py文件导入的包是一个namespace
模块和包有什么不一样呢,模块没有__path__属性,而包有__path__属性,__path__属性可以查看包的路径

  1. import module_a as ma
    这个和import module_a的区别是将ma与module_a绑定起来,使用的时候使用ma,请看下面
    >>> import module_a as ma
    >>> sys.modules['module_a']
    <module 'module_a' (namespace)>
    >>> sys.modules['ma']
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    KeyError: 'ma'
    >>>
    可见,存储到sys.modules中的模块名是原名并非别名
  2. import module_a.module_b vs from module_a import module_b
    import module_a.module_b如下
    >>> import sys
    >>> import module_a.module_b
    >>> sys.modules['module_b']
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    KeyError: 'module_b'
    >>> sys.modules['module_a.module_b']
    <module 'module_a.module_b' (namespace)>
    >>> sys.modules['module_a']
    <module 'module_a' (namespace)>
    >>>from module_a import module_b如下
    >>> import sys
    >>> from module_a import module_b
    >>> sys.modules['module_b']
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    KeyError: 'module_b'
    >>> sys.modules['module_a.module_b']
    <module 'module_a.module_b' (namespace)>
    >>> sys.modules['module_a']
    <module 'module_a' (namespace)>
    >>>由上可见,import module_a.module_b和from module_a import module_b效果是一样的,import子模块的时候,会把父模块import进来,那到底是谁先import呢,下面来做个示例,创建如下文件结构

–module_a
|—__init__.py (里面写上 print(‘module_a’))
|—module_b
|–|—__init__.py(里面写上 print(‘module_b’))

  1. 然后
    >>> import module_a.module_b
    module_a
    module_b
    可知父模块会先被import
  2. from module_a import *
    这个是import module_a,不会import module_a的子模块,这样使用时不需要带上命名空间,如果有多个模块都采取这种import做法,如果有模块的变量名一样的话,会有覆盖的问题,创建如下文件结构
    –module_x
    |—__init__.py (内容如下)
    ’ (from .module1 import *
    ’ from .module2 import *
    ’ print(‘a = ‘, a))
    |—module1.py (内容 a = 2)
    |—module2.py (内容 a = 3)
    输入
    >>> import module_x
    a = 3
    a = 3说明module1的a被覆盖了
    假设是函数会怎样呢
    –module_x
    |—__init__.py (内容如下)
    ’ (from .module1 import *
    ’ from .module2 import *
    ’ func()
    |—module1.py (内容如下)
    ’ (def func():
    ’ print(‘module1’))
    |—module2.py (内容如下)
    ’ (def func():
    ’ print(‘module2’))
    输入
    >>> import module_x
    module2
    可见函数也会被覆盖,若函数名一样,函数参数不一样呢
    –module_x
    |—__init__.py (内容如下)
    ’ (from .module1 import *
    ’ from .module2 import *
    ’ func()
    ’ func(1)
    |—module1.py (内容如下)
    ’ (def func():
    ’ print(‘module1’))
    |—module2.py (内容如下)
    ’ (def func(i):
    ’ print(‘module2’))
    输入
    >>> import module_x
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "F:\yjm\py_learn\import_ex\module_x\__init__.py", line 3, in <module>
    func()
    TypeError: func() missing 1 required positional argument: 'i'
    看来即使函数名一样,函数参数不一样的情况下,函数也会被覆盖,这个应该是python不支持重载导致的
  3. import 多次相同的module
    假设module_c.py的代码是print(‘module_c’),当第一次import module_c的时候会打印module_c,但是以后再import module_c就不会打印了,即除了第一次import外,其余的import都不会执行文件,按照官方文档,import的时候回去sys.modules检查是否存在需要import的module的key,如果存在,则不需要import,但是如果把sys.modules中的module_c删除掉后,import module_c又会打印module_c,所以一个模块可以被import,也可以在import 之后删掉。
    比如在一个py文件中import module_a,另外还有其他py文件import module_a,各个文件共享module_a的成员,每个py文件的module_a都不是独立的
  4. from .module import xxx
    这个module前面有一个点,一个点代表当前目录,两个点代表上一级目录,以此类推,这个是相对当前的py文件来说的,如果直接在终端输入这类的import是错误的,这种import只适用于写在py代码文件中,注意import .module是错误的
  5. import流程
    import module,
    1)先从sys.modules查找是否存在该module,如果存在则import ok,否则
    2)从sys.meta_path中的Finder或者Importer查找,如果查找不到则
    3)再从module.path(import a.b,会从a.path中去找b)和sys.path中按顺序从路径表中查找该module,如果查找到则import,不会再往下查找,所以路径表中包含相同的module时,哪个路径靠前哪个就被import
  6. import hook
    import hook 分两种,一种是meta hook,另一种是path hook。可以通过import hook改变import 机制,下面做个简单的示例,在个module第一次import的时候打印module的path,下面是代码
import sys

class Watcher(object):
    @classmethod
    def find_module(self, name, path, target=None):
        print('importing', name, path, target)
        return None

sys.meta_path.insert(0, Watcher)
然后每次import module(只要这个module不在sys.modules中)都会打印module                                           的path,除了最顶层的module,其他的module都会打印path
  1. import 相关内容
    import 语法
    sys.modules:存储已加载的module
    sys.path:module搜索路径表
    sys.meta_path:module importer表
    package.__path__:package的路径,可更改,用于寻找子模块的路径
    importlib.import_module():加载模块
    importlib.reload():重新加载模块
    import hook:可以改变import module的获取途径,以及module 被import时增加额外的操作