Python-模块化、__all__、import、from ... import ...

1、简介

一般来说,编程语言中,库、包、模块是同一种概念,是代码组织方式。
Python中只有一种模块对象类型,但是为了模块化组织模块的便利,提供了""的概念。
模块module:指的是Python的源代码文件。
包package:指的是模块组织在一起的和包名同名的目录及其相关文件。

2、导入语句

2.2、import语句

2.2.1、语法介绍

语句               含义
import             # 模块1[,模块2,...] 完全导入
import ... as ...  # ///模块别名

2.2.2、简介

1、 找到指定的模块,加载和初始化它,生成模块对象。找不到,抛出异常
2、在import所在的作用域的局部命名空间中,增加名称和上一步创建的对象关联

2.2.3、示例

复制代码
# import ...
import functools # 导入模块
print(dir()) # [..., 'functools']
print(functools, type(functools)) # <module 'functools' from 'path/to/functools.py'> <class 'module'>
print(functools.wraps) # <function wraps at 0x00000000010FB400>

import os.path # 导入os.path,os加入当前名词空间
print(dir()) # [..., 'os']
print(os, type(os)) # <module 'os' from 'path/to/os.py'> <class 'module'>
print(os.path) # 完全限定名称访问path

# import ... as 
import os.path as osp # 导入os.path并赋给osp
print(dir()) #[..., 'osp']
print(osp) # <module 'ntpath' from 'path/to/path.py'>

def testimport():
    import os.path # 局部
    print(dir())

testimport()
print(globals().keys())

import os.stat # 报错:ModuleNotFoundError: No module named 'os.stat'; 'os' is not a package
复制代码

2.2.4、总结

导入顶级模块,其名称会加入到本地名词空间中,并绑定到其模块对象
导入非顶级模块,只将其顶级模块名称加入到本地名词空间中。导入的模块必须使用完全限定名称来访问
如果使用了as,as后的名称直接绑定到导入的模块对象,并将该名称加入到本地名词空间中
import 之后只能是模块类型

2.3、from语句

2.3.1、语法介绍

语句                       # 含义
from ... import ...        # 部分导入
from ... import ... as ... # 别名

2.3.2、示例 

复制代码
from pathlib import Path, PosixPath # 在当前名词空间导入该模块指定的成员
print(dir()) # [..., 'Path', 'PosixPath']

from pathlib import * # 在当前名词空间导入该模块所有公共成员(非下划线开头成员)或指定成员
print(dir()) # [..., 'Path', 'PosixPath', 'PurePath', 'PurePosixPath', 'PureWindowsPath', 'WindowsPath']

from functools import wraps as wr, partial # 别名
print(dir()) # [..., 'wr', 'partial']

from os.path import exists # 加载、初始化os、os.path模块,exists加入本地名词空间并绑定
if exists('c:/t'):
    print('Found')
else:
    print('Not Found')
print(dir())
print(exists)


# 4种方式获得同一个对象exists
import os
print(os.path.exists)
print(exists)
print(os.path.__dict__['exists']) # 字符串
print(getattr(os.path, 'exists')) # 字符串
复制代码

2.3.3、总结

复制代码
找到from子句中指定的模块,加载并初始化它(注意不是导入)
对于import子句后的名称
1、先查from子句导入的模块是否具有该名称的属性
2、如果不是,则尝试导入该名称的子模块
3、还没有找到,则抛出ImportError异常
4、这个名称保存到本地名词空间中,如果有as子句,则使用as子句后的名称

from pathlib import Path  # 导入类Path

print(Path, id(Path))
import pathlib as pl  # 导入模块使用别名

print(dir())
print(pl)
print(pl.Path, id(pl.Path))
# 可以看出导入的名词Path和pl.Path是同一个对象
复制代码

3、自定义模块

3.1、简介

自定义模块:.py文件就是一个模块

3.2、示例

复制代码
# test1.py文件
print('This is test1 module')
class A:
    def showmodule(self):
        print(1, self.__module__, self)
        print(2, __class__, id(__class__))
a = A()
a.showmodule()

# test2.py文件
import test1
a = test1.A()
a.showmodule()

# test3.py文件
from test1 import A as cls
a = cls()
a.showmodule()
复制代码

3.3、自定义模块命名规范

1、模块名就是文件名
2、模块名必须符合标识符的要求,是非数字开头的字母、数字和下划线的组合。test-module.py这样的文件名不能作为模块名。也不要使用中文。
3、不要使用系统模块名来避免冲突,除非你明确知道这个模块名的用途
4、通常模块名为全小写,下划线来分割

4、模块特征

4.1、模块搜索顺序

4.1.1、示例:使用 sys.path 查看搜索顺序

import sys
print(*sys.path, sep='\n')
显示结果为,python模块的路径搜索顺序
当加载一个模块的时候,需要从这些搜索路径中从前到后依次查找,并不搜索这些目录的子目录。
搜索到模块就加载,搜索不到就抛异常
路径也可以为字典、zip文件、egg文件。
.egg文件,由setuptools库创建的包,第三方库常用的格式。添加了元数据(版本号、依赖项等)信息的zip文件

4.1.2、路径顺序

1、程序主目录,程序运行的主程序脚本所在的目录
2、PYTHONPATH目录,环境变量PYTHONPATH设置的目录也是搜索模块的路径
3、标准库目录,Python自带的库模块所在目录
sys.path可以被修改,增加新的目录

4.2、模块的重复导入

4.2.1、示例

复制代码
# test1.py文件
print('This is test1 module')
class A:
    def showmodule(self):
        print(1, self.__module__, self)
        print(2, __class__, id(__class__))

a = A()
a.showmodule()

# test2.py文件
import test1
print('local module')
import test1
import test1
复制代码

4.2.2、分析

从执行结果来看,不会产生重复导入的现象。
所有加载的模块都会记录在sys.modules中,sys.modules是存储已经加载过的所有模块的字典。
打印sys.modules可以看到builtins、os、os.path、sys等模块都已经加载了。

4.3、模块运行

4.3.1、简介

__name__ ,每个模块都会定义一个 __name__ 特殊变量来存储当前模块的名称,如果不指定,则默认为源代码文件名,如果是包则有限定名。
解释器初始化的时候,会初始化sys.modules字典(保存已加载的模块),加载builtins(全局函数、常
量)模块、 __main__ 模块、sys模块,以及初始化模块搜索路径sys.path
Python是脚本语言,任何一个脚本都可以直接执行,也可以作为模块被导入。
当从标准输入(命令行方式敲代码)、脚本($ python test.py)或交互式读取的时候,会将模块的
__name__ 设置为 __main__ ,模块的顶层代码就在 __main__ 这个作用域中执行。顶层代码:模块中缩进最外层的代码。
如果是import导入的,其 __name__ 默认就是模块名

4.3.2、示例

复制代码
# test1.py文件
import test2

# test2.py文件
# 判断模块是否以程序的方式运行 $python test.py
if __name__ == '__main__':
    print('in __main__') # 程序的方式运行的代码
else:
    print('in imported module') # 模块导入的方式运行的代码
复制代码

4.3.3、if __name__ == '__main__': 用途

1、本模块的功能测试
对于非主模块,测试本模块内的函数、类
2、避免主模块变更的副作用
顶层代码,没有封装,主模块使用时没有问题。但是,一旦有了新的主模块,老的主模块成了被导入模块,由于原来代码没有封装,一并执行了。

4.4、模块的属性

4.4.1、属性介绍

属性           含义
__file__       # 字符串,源文件路径
__cached__     # 字符串,编译后的字节码文件路径
__spec__       # 显示模块的规范
__name__       # 模块名
__package__    # 当模块是包,同 __name__ ;否则,可以设置为顶级模块的空字符串

4.4.2、示例

import os as t3

for k, v in t3.__dict__.items():
    print(k, str(v)[:80])
print(dir(t3))
for name in dir(t3):
    print(getattr(t3, name))

5、包

5.1、简介

包,即特殊的模块

5.2、Python模块支持目录吗?

Python中,目录可以作为模块,这就是包,不过代码需要写在该目录下 __init__.py 中。包的 __file__ 就指向 __init__.py 这个文件。

5.3、子模块

5.3.1、简介

包目录下的py文件、子目录都是其子模块
m
|-- __init__.py
|-- m1.py
|-- m2
   |-- __init__.py
   |-- m21
       |-- __init__.py
   |-- m22.py

5.3.2、示例

复制代码
# 注意观察已经加载的模块、当前名词空间的名词
# import m
# import m.m1
# import m.m2.m21
# from m import m1
from m.m2 import m21

print('-' * 30)
print(*filter(lambda x: x.startswith('m'), dir()))
print('-' * 30)
import sys

print(sorted(filter(lambda x: x.startswith('m'), sys.modules.keys())))

# ------------------------------
# m21
# ------------------------------
# ['m', 'm.m2', 'm.m2.m21', 'marshal']

删除 __init__.py 试一试,可以发现删除并不影响导入,但是这不是良好的习惯,请保留__init__.py 文件
复制代码

5.4、模块和包的总结

复制代码
包能够更好的组织模块,尤其是大的模块,其代码行数很多,可以把它拆分成很多子模块,便于使用某些功能就加载相应的子模块。

包目录中 __init__.py 是在包第一次导入的时候就会执行,内容可以为空,也可以是用于该包初始化工
作的代码,最好不要删除它(低版本不可删除 __init__.py 文件)
导入子模块一定会加载父模块,但是导入父模块一定不会导入子模块
包目录之间只能使用.点号作为间隔符,表示模块及其子模块的层级关系
模块也是封装,如同类、函数,不过它能够封装变量、类、函数。
模块就是命名空间,其内部的顶层标识符,都是它的属性,可以通过 __dict__ 或dir(module)查看。
包也是模块,但模块不一定是包,包是特殊的模块,是一种组织方式,它包含 __path__ 属性
复制代码
问题
from json import encoder 之后, json.dump 函数用不了,为什么?
原因是from json import encoder之后,当前名词空间没有json,但是json模块已经加载过了,没有
json的引用,无法使用dump函数。

import json.encoder 之后呢? json.dump 函数能用吗?
import json.encoder也加载json模块,但是当前名词空间有json,因此可以调用json.dump。

6、绝对导入、相对导入

6.1、绝对导入

在import语句或者from导入模块,模块名称最前面不是以.点开头的
绝对导入总是去模块搜索路径中找,当然会查看一下该模块是否已经加载

6.2、相对导入

只能用在from语句中
使用.点号,表示当前目录内
.. 两点表示上一级目录
... 三点表示上上一级
只在包内使用,一般不要在顶层模块中使用相对导入
一旦一个模块中使用相对导入,就不可以作为主模块运行了
# 举例a.b.c模块,a、b是目录,c是模块c.py
# c的代码如下
from . import d # imports a.b.d
from .. import e # imports a.e
from .d import x # a.b.d.x
from ..e import x # a.e.x
相对导入,更像是目录操作。

7、访问控制

7.1、下划线开头的模块名

_ 或者 __ 开头的模块是否能够被导入呢?
创建文件名为 _xyz.py 或者 __xyz.py 测试。
都可以成功的导入,因为它们都是合法的标识符,就可以用作模块名。

7.2、模块内的标识符

# xyz.py
print(__name__)
A = 5
_B = 6
__C = 7
__my__ = 8
# test.py中
import xyz
import sys
print(sorted(sys.modules.keys()))
print(dir())
print(xyz.A, xyz._B, xyz.__C, xyz.__my__)
普通变量、保护变量、私有变量、特殊变量,都没有被隐藏,也就是说模块内没有私有的变量,在模块中定义不做特殊处理。
# test.py中
# from语句导入
from xyz import A, _B as B, __my__, __C as C
import sys
print(sorted(sys.modules.keys()))
print(dir())
print(A, B, __my__, C)
依然可以使用from语句,访问所有变量

7.3、from ... import * 和 __all__

7.3.1、使用from ... import * 导入

# xyz.py

print(__name__)
A = 5
_B = 6
__C = 7
__my__ = 8
# test.py中
from xyz import *
import sys
print(sorted(sys.modules.keys()))
print(dir())
print(locals()['A'])
A = 55
print(locals()['A']) # 思考这个A是谁的A了
结果是只导入了A,下划线开头的都没有导入

7.3.2、使用 __all__

__all__ 是一个列表,元素是字符串,每一个元素都是一个模块内的变量名
复制代码
# xyz.py中
__all__ = ["X", "Y"]
print(__name__)
A = 5
_B = 6
__C = 7
__my__ = 8
X = 10
Y = 20
复制代码
# test.py中
from xyz import *
import sys
print(sorted(sys.modules.keys()))
print(dir())
#print(locals()['A']) # 报错
print(locals()['X'])
print(locals()['Y'])

7.3.3、修改 __all__ 列表,加入下划线开头变量,看看什么效果

复制代码
# xyz.py中
__all__ = ["X", "Y", "_B", "__C"]
print(__name__)
A = 5
_B = 6
__C = 7
__my__ = 8
X = 10
Y = 20
复制代码
复制代码
# test.py中
from xyz import *
import sys
print(sorted(sys.modules.keys()))
print(dir())
#print(locals()['A'])
print(locals()['X'])
print(locals()['Y'])
print(locals()['_B'])
print(locals()['__C'])
复制代码
可以看到使用from xyz import *导入 __all__ 列表中的名称

8、总结

8.1、使用 from xyz import * 导入

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

8.2、from module import name1, name2 导入

这种方式的导入是明确的,哪怕是导入子模块,或者导入下划线开头的名称
程序员可以有控制的导入名称和其对应的对象

 

posted @   小粉优化大师  阅读(69)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示