1. 包管理
1.1 一些概念
模块(module):Python中可复用的基本代码单元,可由其他代码import的一块代码。
模块的类型
- pure Python module:由python编写的模块,包含在单独的py文件中(或者是pyc/pyo文件)。
- extension module:由实现Python的底层语言编写的模块(C/C++ for Python, Java for Jython)。通常包含在单独的动态加载文件中,比如Unix中的so文件,windows中的DLL文件,或者是Jython扩展的java类文件。(注意,目前为止Distutils只能处理Python的C/C++扩展)
包(package):模块的集合,多个模块文件(即.py文件)所在的目录中包含__init__
.py文件,则该目录是一个package。
1.2 __init__
.py文件的作用
- 将目录变成package:目录有了
__init__
.py则成了一个package - 控制导入行为:指定import *时暴露哪些模块或者对象
- 执行初始化工作:其他文件在import时会执行
__init__
.py中的Python代码。
case1: 将添加自定义package
# package content tree
mypkg
├── __init__.py
├── module0.py
└── module1.py# __init__.py
class BaseModel(Singleton, Utils):pass# module0.py
from . import BaseModel
class MysqlModel(BaseModel):pass# module1.py
from . import BaseModel
class RedisModel(BaseModel):pass# add package path to sys.path so that it can be searched and imported by other module
import sys
sys.path.append('path/to/pkg')
from pkg import MysqlModel
case2: 控制导入行为
# package content tree
mypkg
├── __init__.py
├── module0.py
└── module1.py# __init__.py
__all__ = ['module0']
from . import module1
from . import module2# other.py
from pkg import * # 只能使用module0中的对象
case3: 执行初始化工作
- 定义package中其他module都可以使用的全局变量
# package content tree
mypkg
├── __init__.py
├── module0.py
└── module1.py# __init__.py
GLOBAL_VARIABLE = 100# module0.py
from . import GLOBAL_VARIABLEdef show_gvar():print(GLOBAL_VARIABLE)
- 初始化数据库连接
# package content tree
mypkg
├── __init__.py
├── module0.py
└── module1.py# __init__.py
DB_CONNECTION = sqlite3.connect('example.db')# module0.py
from . import DB_CONNECTIONdef create_table():cursor = DB_CONNECTION.cursor()cursor.execute('''CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY, name TEXT)''')DB_CONNECTION.commit()
- 加载配置文件
# package content tree
proj
├── conf
│ └── config.ini
└── pkg├── __init__.py├── module0.py└── module1.py# config.ini
[settings]
debug = true# __init__.py
import configparserconfig = configparser.ConfigParser()
config.read('config.ini')
CONFIG = {'debug': config.getboolean('settings', 'debug')
}# module0.py
from . import CONFIGdef do_something():if CONFIG['debug']:print('Debug mode is on')else:print('Debug mode is off')
- package之间统一的日志对象
# package content tree
mypkg
├── __init__.py
├── module0.py
└── module1.py# __init__.py
import logginglogging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)# module0.py
from . import loggerdef log_message():logger.info('This is an info message')
1.3 模块的搜索
python通过搜索sys.path来寻找模块。sys.path存放了5类路径:项目根目录,$PYTHONPATH,标准库目录,扩展模块目录,第三方库目录。
sys.path示例,其中PYTHONPATH为空
/project_root # 项目路径
/path/to/3.12.1/lib/python312.zip # 压缩文件形式的标准库,是标准库的一部分
/path/to/3.12.1/lib/python3.12 # 未经压缩的标准库,是标准库的一部分
/path/to/3.12.1/lib/python3.12/lib-dynload # 动态加载形式的扩展模块(.so文件)
/path/to/3.12.1/envs/env/lib/python3.12/site-packages # 第三方库
sys.path示例,其中PYTHONPATH不为空
# 查看sys.path
import sys
print(sys.path)'''before
/xxx/project_root
/path/to/3.12.1/lib/python312.zip
/path/to/3.12.1/lib/python3.12
/path/to/3.12.1/lib/python3.12/lib-dynload
/path/to/3.12.1/envs/env/lib/python3.12/site-packages
'''# add /xxx/project_root/libs to PYTHONPATH
export PYTHONPATH=/xxx/project_root/libs:$PYTHONPATH'''after
/xxx/project_root
/xxx/project_root/libs
/path/to/3.12.1/lib/python312.zip
/path/to/3.12.1/lib/python3.12
/path/to/3.12.1/lib/python3.12/lib-dynload
/path/to/3.12.1/envs/env/lib/python3.12/site-packages
'''
2. 其他话题
一、__main__
现有脚本文件a.py和b.py,a中import了b,执行a的时候,b中的代码也被执行。每个文件都有属性__name__
,主动执行的文件,其__name__
为__main__
,
而因为import而被执行的文件,其 __name__
为文件名(不带后缀)。此例,a主动执行,a.__name__
为__main__
,b因为import而被执行,b.__name__
为b。
如想让部分代码导入而不执行且只有在主动执行时才实际执行,则应把这部分代码置于语句if __name_
== '__main__
'下。这表明,该脚本文件作为模块
被导入其他文件中,执行if __name__
== '__main__
'时,判断为否(__name__
为木模块名),不会执行if语句内的代码。简言之,执行流源于本文件,那么
__name__
为__main__
,执行流源于其他文件,则__name__
为模块名。
二、__pycache__
Python解释器将源码转换为字节码,然后再由解释器来执行这些字节码。
解释器的具体工作:
1、完成模块的加载和链接;
2、将源代码编译为PyCodeObject对象(即字节码),写入内存中,供CPU读取并执行;
3、cpu从内存中读取并执行PyCodeObject,然后将PyCodeObject写回硬盘当中,形成.pyc或.pyo字节码文件。之后若再次执行该脚本,解释器先检查本地是否有对应的字节码文件,该字节码文件的修改时间是否在其源文件之后,是就直接执行,否则重复上述步骤。
第一次执行代码的时候,Python解释器已经把编译的字节码放在__pycache__
文件夹中,这样以后再次运行的话,如果被调用的模块未发生改变,那就直接跳过编译这一步,直接去__pycache__
文件夹中去运行相关的*.pyc文件,大大缩短了项目运行前的准备时间。