模块、包、库
import和from ... import ...及as
模块搜索顺序sys.path、模块预加载sys.module、模块的重复导入
模块的属性__file__ 和 __name__ 及if __name__ == '__main__':
相对导入和绝对导入及__init__.py
模块中的访问控制,from ... import *__all__

模块、包、库


编程语言中的库,库、包、模块可以理解为同一概念,都是代码的一种组织方式

  • 模块module:一个.py文件就是一个模块
  • 包package:在模块的基础上,将模块组织在一起的(一个个.py文件),和包名同名的目录及其相关文件。包目录下必须包含__init__.py文件,包目录下可包含子目录,若子目录中也包含__init__.py文件,则可成为子包
  • 库:具有相关功能模块的集合。这也是python特色之一,python具有强大的标准库(built-in)如os,sys等、第三方库(如requests等)及自定义库

查看属性dir()

可以使用dir()来查看模块的属性,对于不同的作用域还可使用locals()和globals()
详情可参看魔法函数查看属性dir()和__dict__

导入语句


import和from...import...

import语句

  • import 只能是module,不能是类或函数
  • import 找到指定的模块,加载和初始化它,生成模块对象。并在import所在的作用域的局部命名空间中,增加名称和其创建的对象关联
  • 导入顶级模块,如import os,其名称os会加到本地名词空间中,并绑定到其模块对象
  • 导入非顶级模块,如import os.path,只将其顶级模块名称os加入到本地名词空间中。导入的模块必须使用完全限定名称来访问os.path
import functools  # import后只能是模块
# import functools.wraps  # No module named 'functools.wraps'; 'functools' is not a package                                 
                                                                 
print(dir())  # [..., 'functools']                               
print(functools)  # <module 'functools' from ...>                
print(functools.wraps)  # <function wraps at 0x00000000010FB400> 
import os.path  # 虽然导入了os.path,但需要加载os(父模块)和os.path(子模块)

print(os)  # <module 'os' from ...>
print(os.path)  # <module 'posixpath' from....>
print(dir())  # [...., 'os']只有顶级模块加入到了本地名词空间中
print(os.path.exists('abc'))  # 导入的模块必须使用完全限定名称来访问os.path
def test_import():             
    import os.path       # 局部       
    print(dir())  # ['os']     
                               
                               
test_import()                  
print(dir())  # 无os            
print(globals().keys())  # 无os 

from 语句

  • from ... import ... : from module import [ module | class | function | 其他如全局变量等]
    • 找到from子句中指定的模块,加载并初始化它(注意不是导入)
    • import子句后的名称:先查from子句导入的模块是否具有该名称的属性->否则尝试导入该名称的子模块-> 否则抛出ImportError异常
    • import子句后的名称保存到本地名词空间中
from pathlib import Path # 导入类Path                   
from os.path import exists  # 到如exists函数             
print(Path, id(Path))                                
                                                     
print(dir())   # ['Path',...,'exitsts']              
print(Path)    # <class 'pathlib.Path'>              
print(exists)    # <function exists at 0x10b482f70> 

理解import和from的区别

from json import encoder

print(encoder)
print(dir())  # [..., encoder, ...]
d = {'a': 1}
# print(json.dumps(d)) # NameError: name 'json' is not defined


import json.encoder

print(json.encoder)
print(dir())  # [..., json, ...]
d = {'a': 1}
print(json.dumps(d))

from json import encode : 当前名词空间没有json,但是json模块已经加载过了,没有json的引用,无法使用dumps函数
import json.encoder:也加载json模块,但是当前名词空间有json,因此可以调用json.dumps

as子句

  • import...as:as后的名称直接绑定到导入的模块对象,并将该名称加入到本地名词空间中
  • from ... import ... as:as子句后的名称保存到本地名词
import pathlib as pl  # 导入模块使用别名

print(dir())
print(pl)
print(pl.Path, id(pl.Path))

from pathlib import Path as pp # 导入类Path
print(pp, id(pp))
print(dir())
# 可以看出导入的名词pp和pl.Path是同一个对象

自定义模块


# t1.py文件,可认为是一个模块
print('this is t1 module')
x = 100

class A:
    def showmodule(self):
        print(1, self.__class__.__module__)
        print(2, self.__dict__)
        print(3, self.__class__.__dict__)
        print(4, self.__class__.__name__)

a = A()
a.showmodule()

# t2.py文件
import t1

print(t1.a)
print(t1.x)
print(t1.A)
print(t1.__dict__['A'])
print(t1.__dict__.get('A'))
print(getattr(t1, 'A'))

print(dir(t1))
print(dir())
------执行t2后输出----------
this is t1 module
1 t1
2 {}
3 {'__module__': 't1', 'showmodule': <function A.showmodule at 0x10e1a43a0>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
4 A
<t1.A object at 0x10e192640>
100
<class 't1.A'>
<class 't1.A'>
<class 't1.A'>
<class 't1.A'>
['A', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'a', 'x']
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 't1']

模块有严格的边界,无法突破

  • 模块命名规则:通常模块名为全小写,下划线来分割;不要使用系统模块名来避免冲突

模块搜索顺序


  • sys.path:python模块的路径搜索顺序
import sys

# print(*sys.path, sep='\n')
for p in sys.path:
    print(p)
  • 可以看出模块的搜索顺序是:
    • 程序主目录,程序运行的主程序脚本所在的目录
    • PYTHON PATH目录,环境变量PYTHON PATH设置的目录也是搜索模块的路径
    • 标准库目录,Python自带的库模块所在目录

当加载一个模块的时候,需要从这些搜索路径中从前到后依次查找,并不搜索这些目录的子目录
搜索到模块就加载,搜索不到就抛异常
路径也可以为字典、zip文件、egg文件

  • sys.path返回的是list,因此可以被修改即搜索模块顺序可以被修改,但不建议这么做
  • sys.path首先搜索路径是程序运行的主程序目录,因此,一般自定义模块不建议定义与常用模块同名,否则会造成冲突,常用模块的功能被拦截

模块的重复导入


  • 所有已加载的模块都会记录在sys.modules中
  • sys.modules是存储已经加载过的所有模块的字典
  • 模块并不会重复导入
# t1.py文件
print('this is t1 module')

class A:

    def showmodule(self):
        print(1, self.__class__.__module__)
        print(2, self.__dict__)
        print(3, self.__class__.__dict__)
        print(4, self.__class__.__name__)

a = A()
a.showmodule()

# t2.py文件
print('this is t2 module')
import t1
print('~~~~~')
import t1
print('~~~~~')
import t1
print('~~~~~')

从上例执行结果来看不会出现重复导入的现象

# sys.modules存储已经加载过的所有模块的字典
import sys
print(*sys.modules.keys(), sep='\n')
import os
print(os.path.exists('111'))

从sys.modules打印的结果可以看到os、os.path都已经预加载了,因此尽管只import os,也可以使用os.path

总结:模块搜索顺序,先查看sys.modules中是否已经预加载,然后再按sys.path顺序查找,找不到则抛出异常

模块的属性

# 在模块中如t1.py
print(dir())
print(globals())
--------------
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x102a573a0>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'xxx/t3.py', '__cached__': None}

这里主要关注__name____file__

__name____file__

  • __name__:模块名。显示当前模块执行过程中的名称,如果当前程序在这个模块中执行,__name__就是__main__,否则就是这个模块的名称
# t1.py文件
print('this is t1 module')

# t2.py文件
import t1

print(1, globals())
print(2, __name__)
print(3, t1.__name__)
----------执行t2输出结果--------
this is t1 module
1 {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x1038a63a0>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/Users/tina/PycharmProjects/pypractice/t3.py', '__cached__': None, 't1': <module 't1' from '/Users/tina/PycharmProjects/pypractice/t1.py'>}
2 __main__
3 t1
  • __file__: 文件(模块)当前位置(路径)
import os
from pathlib import Path

print(__file__)
p = Path(__file__)
print(p)

t1_path = ''
if os.path.exists('t1.py'):
    t1_path = p.with_name('t1.py')

print(t1_path)

if __name__ == '__main__':的用途

  1. 本模块的功能测试
    对于非主模块,测试本模块内的函数、类
  2. 避免主模块变更的副作用
    若没有封装,主模块使用时是没有问题的。但当使用新的主模块并导入老的主模块时,老的主模块不封装的话,一些测试代码等不必要的代码就会一并执行了

包package


  • 特殊的模块,一个带有__init__.py文件的目录即包
  • 包也是模块,但模块不一定是包,包是特殊的模块,是一种组织方式,它包含 __path__ 属性
# 如下目录关系
'''
├── m
│   ├── __init__.py
│   ├── m1.py
│   └── m2
│       ├── __init__.py
│       ├── m21
│       │   └── __init__.py
│       └── m22.py
└── t1.py
'''
# t1.py文件
import m.m2.m22
print(dir(m))
print(m.__path__)
print(dir(m.m2.m22))

__init__.py

  • 包目录中 __init__.py在包第一次导入的时候就会执行
  • 内容可以为空,也可以是用于该包初始化工作的代码,最好不要删除(低版本python不可删除__init__.py 文件)

子模块

  • 导入子模块一定会加载父模块,但是导入父模块一定不会导入子模块
  • 包能够更好的组织模块,尤其是大的模块,其代码行数很多,可以把它拆分成很多子模块,便于使用某些功能就加载相应的子模块
  • 包目录之间只能使用.点号作为间隔符,表示模块及其子模块的层级关系

绝对导入和相对导入


绝对导入

  • 在import语句或者from导入模块,模块名称最前面不是以.点开头的

相对导入

  • 只在包内使用,包内部的文件之间互相导入可以使用相对导入,可以通过提前把函数、常量、类导入到__init__.py中再在其他文件中导入,可以简化代码
  • 只能用在from语句中
  • 相对导入,像是目录操作。使用.点号,表示当前目录内;..表示上一级目录
  • 一个模块中使用相对导入,就不可以作为主模块运行了(ImportError: attempted relative import with no known parent package)

模块中的访问控制


# t1.py文件
print(__name__)
a = 100
_b = 200
__c = 300
__A__ = 400

# t2.py文件
import t1
print(t1.a, t1._b, t1.__c, t1.__A__)

# from t1 import a, _b, __c, __A__ # 都能正常导入
  • 普通变量、保护变量、私有变量、特殊变量,都没有被隐藏。也就是说模块内没有私有的变量,在模块中定义不做特殊处理

from ... import *

# t1.py文件
print(__name__)
a = 100
_b = 200
__c = 300
__A__ = 400

# t2.py文件
from t1 import *
import sys

print(sorted(sys.modules.keys()))
print(dir())
print(locals()['a'])
a = 1000
print(locals()['a'])

结果是只导入了A,下划线开头的都没有导入

__all__

print(__name__)
__all__ = ['a', '_b', '__c']  # must be str
a = 100
_b = 200
__c = 300
__A__ = 400

# t2.py文件
from t1 import *
import sys

print(sorted(sys.modules.keys()))
print(dir())

可以看到使用from ... import *导入 __all__ 列表中的名称

  • 如果模块没有 __all__from ... import *只导入非下划线开头的该模块的变量。如果是包,子模块也不会导入,除非在__all__中设置,或 __init__.py中导入它们
  • 如果模块有 __all__from ... import * 只导入 __all__ 列表中指定的名称,哪怕这个名词是下划线开头的,或者是子模块
  • from ... import *方式导入,使用简单,但是其副作用是导入大量不需要使用的变量,甚至有可能造成名称的冲突。而 __all__可以控制被导入模块在这种导入方式下能够提供的变量名称,因此,编写模块时,应该尽量加入 __all__

参考


  • magedu