## 模块导入、加载
python中一个py文件就是一个模块。一个package或者一个文件夹就是一个包。
当前包中有哪些可用的属性可以通过dir()方法来查看。
### dir()
```python
import json
if __name__ == '__main__':
# 获取当前模块的属性列表
print(dir()) # ['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'json']
```
dir打印的是当前模块的属性列表。可以看到列表中包含一个json,这其实是json模块的一个标识符。打印json可以看到一个module对象。
```python
import json
if __name__ == '__main__':
print(json) # <module 'json' from 'C:\\Users\\amir\\.pyenv\\pyenv-win\\versions\\3.9.6\\lib\\json\\__init__.py'>
```
### import和from...import
> import语句:import后只能写模块,不能写函数或者类之类的
>
> from...import...: from后只能写模块,但是这个import之后可以写类、函数、变量
>
> from...import *: 表示导入所有允许访问的对象,一般的,允许访问的会用 `__all__`来定义
```python
# test2.py
from typing import Union
x = 123
def add(a: Union[int, float], b: Union[int, float]):
if isinstance(a, (int, float)) and isinstance(b, (int, float)):
return a + b
else:
raise Exception("非法参数")
class Student:
name = "123"
age = "456"
```
```python
# test1.py
from test2 import Student, x, add
if __name__ == '__main__':
print(Student)
print(x)
print(add(1, 2.3))
```
```python
# test3.py
__all__ = ["Genius"]
class Genius:
name = "amir"
age = 18
class Idiot:
name = "jianwa"
age = 99
```
```python
# test4.py
from test3 import *
print(dir()) # ['Genius', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
```
### import在干什么
import是在加载被导入模块的内容,这个加载过程为解释器执行该模块的代码。
```python
__all__ = ["Genius"]
class Genius:
name = "amir"
age = 18
class Idiot:
name = "jianwa"
age = 99
print("生存还是毁灭?")
```
当其他文件导入这个模块的时候,我们可以看到,最优先的会打印上述文字。import还会将所有的类|函数|变量装载到内存中,生成一个个对象。
### 访问一个对象的几种方式
```python
# test2.py
__all__ = ["html"]
html = """<li>this is a li tag</li>"""
```
```python
# test1.py
import test2
print(test2.html)
print(getattr(test2, "html"))
print(test2.__dict__["html"])
```
## 模块的搜索顺序
```python
import sys
print(*sys.path, sep="\n")
```
可以通过sys.path看到一个路径列表,这就是模块的加载顺序。注意已经加载过的模块不会重复加载(多次import没用),这点可以在sys.modules中查看,已经加载过的被记录了,所以不用重复加载。实际上程序启动的时候,modules中就会主动加载很多模块,比如buildins,os,异常类等,因为python可能会自己调用自己的一些东西。
总体来说,模块的加载顺序是这样的:
- 首先是程序主目录,程序运行的主程序脚本所在的目录
- PYTHONPATH目录,通过环境变量设置的
- 标准库目录,python自带的标准库所在的目录
## 包的导入
导入一个包(package),默认会执行包下面的`__init__.py`,但是下面的子模块、子包不会被执行。可以通过dir或者sys.modules查看。
比如创建一个包demo:
```python
import demo
print(dir())
import sys
# ('demo', <module 'demo' from '/Users/maningyu/workspace/pythonprojects/machine_learning/demo/__init__.py'>)
print(*filter(lambda x: x[0].startswith("d"), sys.modules.items()), sep="\n")
```
可以看到demo也是一个模块对象,指向demo下的init文件。
但是包下面的模块如果没有在init文件中导入,则不会被加载,需要声明导入。
如果是如下类型的导入:
`from demo import m1` demo包下有一个m1.py文件,可以看在sys.modules中看到demo实际上也被加载了,但是无法直接使用demo,因为dir中并没有这个标识符。
> 总之:sys.modules可以看到哪些模块被缓存了,但是dir可以看到哪些模块可以被使用。
假如有一个包,里面有init文件和一些其他的模块文件,怎样在只import包的情况下能使用到其他子模块呢?
可以在包的init文件中通过相对导入的方式导入其他模块:`from . import m1`,这样就能在主模块中使用`demo.m1.xxx`
## 绝对导入和相对导入
### 绝对导入
- import或者from语句中,模块不是以点(.)开头的就是绝对导入。
- 绝对导入首先看模块是否缓存过,如果没有则是按照模块的搜索路径来查找模块
### 相对导入
- 只能用在from语句中,from后以点开头的模块名称就是相对导入。
- <u>**首先代码的顶层目录不要使用相对导入**</u>(顶层为代码main函数存在的位置),原因如下:
![image-20220922204911572](https://note-1302735599.cos.ap-guangzhou.myqcloud.com/ai/image-20220922204911572.png)
启动test2的导入语句为 `from . import test3`,其他都为绝对导入。运行main模块后,报错如下:
```txt
/Users/maningyu/workspace/pythonprojects/machine_learning/venv/bin/python /Users/maningyu/workspace/pythonprojects/machine_learning/main.py
Traceback (most recent call last):
File "/Users/maningyu/workspace/pythonprojects/machine_learning/main.py", line 1, in <module>
import test1
File "/Users/maningyu/workspace/pythonprojects/machine_learning/test1.py", line 1, in <module>
import test2
File "/Users/maningyu/workspace/pythonprojects/machine_learning/test2.py", line 1, in <module>
from . import test3
ImportError: attempted relative import with no known parent package
```
这个时候不需要问为什么,可以从报错中看到"尝试通过相对导入的方式导入,但是没有找到它的父包",推测出一个设计规范,只有非顶层模块才支持相对导入。
- 一个点表示当前目录内
- 两点表示上一级目录
- 三个点表示上上一级
## 访问控制
```python
# test.py
name = "amir"
_age = 10
__gender = 1
__weight__ = 1
```
```python
# main.py
import test
print(test.name)
print(test._age)
print(test.__gender)
print(test.__weight__)
```
可以看到,导入test,其定义的全局变量都可以使用。
但是导入语句换成`from test import *`,发现只能使用非下划线的变量。这就是一种规则,加下划线就是希望只能本模块使用。
```python
from test import *
print(name)
# print(_age)
# print(test.__gender)
# print(test.__weight__)
```
注意如果一个文件中定义了`__all__`,则导入该模块能使用的属性只有all中定义的。
> 不建议使用import * 这样的语句,因为无法控制,可能会存在变量污染等问题。
## 包管理
python的模块源文件发送给别人就可以使用,但是这样不方便。我们可以选择发布到网络中,供别人使用。
Pypi是python公共模块的存储中心。
打包工具有distutils,setuptools和pip,依次构建在前者之上。wheel是基于zip打包的扩展名为.whl的文件。它可以让python库以二进制的方式安装,而不需要本地编译。
打包指南可以参考官方文档:https://docs.python.org/3.7/distributing/index.html
项目结构要求
```tex
packaging_tutorial/
├── LICENSE
├── pyproject.toml
├── README.md
├── src/
│ └── example_package_YOUR_USERNAME_HERE/
│ ├── __init__.py
│ └── example.py
└── tests/
```
具体怎么上传可以参看官方文档。