第90集:配置文件操作
学习目标
- 了解常见的配置文件格式(INI、JSON、YAML、TOML)
- 掌握configparser模块处理INI配置文件
- 学会使用json模块处理JSON配置文件
- 了解PyYAML处理YAML配置文件
- 掌握toml处理TOML配置文件
- 学会配置文件的读取、修改和保存
一、配置文件基础
1.1 什么是配置文件
配置文件是用于存储应用程序配置信息的文本文件,通常包含键值对、嵌套结构等。配置文件使得应用程序可以在不修改代码的情况下调整行为。
1.2 常见的配置文件格式
- INI:传统的配置文件格式,简单易读
- JSON:现代配置格式,支持复杂数据结构
- YAML:人类可读的配置格式,支持注释
- TOML:简洁的配置格式,Python生态常用
- XML:结构化配置格式,较为复杂
1.3 配置文件的应用场景
- 应用程序设置
- 数据库连接信息
- API密钥和令牌
- 日志配置
- 功能开关
二、INI配置文件
2.1 INI文件格式
INI文件由节(section)和键值对组成,格式如下:
[DEFAULT]
key1 = value1
key2 = value2
[section1]
key3 = value3
key4 = value4
[section2]
key5 = value5
key6 = value62.2 configparser模块
configparser是Python标准库中处理INI配置文件的模块。
创建INI配置文件
import configparser
# 创建配置对象
config = configparser.ConfigParser()
# 添加节
config['DEFAULT'] = {
'debug': 'True',
'log_level': 'INFO'
}
config['database'] = {
'host': 'localhost',
'port': '3306',
'username': 'admin',
'password': 'secret'
}
config['server'] = {
'host': '0.0.0.0',
'port': '8080'
}
# 保存到文件
with open('config.ini', 'w') as f:
config.write(f)读取INI配置文件
import configparser
# 创建配置对象
config = configparser.ConfigParser()
# 读取配置文件
config.read('config.ini')
# 获取节
print(config.sections())
# 获取键
print(config.options('database'))
# 获取键值对
print(config.items('database'))
# 获取值
host = config.get('database', 'host')
port = config.getint('database', 'port')
debug = config.getboolean('DEFAULT', 'debug')
print(f"Host: {host}")
print(f"Port: {port}")
print(f"Debug: {debug}")修改INI配置文件
import configparser
# 读取配置文件
config = configparser.ConfigParser()
config.read('config.ini')
# 修改值
config.set('database', 'host', '192.168.1.100')
config.set('database', 'port', '5432')
# 添加新的键值对
config.set('server', 'ssl', 'True')
# 保存修改
with open('config.ini', 'w') as f:
config.write(f)添加和删除节
import configparser
config = configparser.ConfigParser()
config.read('config.ini')
# 添加新节
config['new_section'] = {
'key1': 'value1',
'key2': 'value2'
}
# 删除节
if 'old_section' in config:
del config['old_section']
# 保存
with open('config.ini', 'w') as f:
config.write(f)检查配置项
import configparser
config = configparser.ConfigParser()
config.read('config.ini')
# 检查节是否存在
if 'database' in config:
print("database节存在")
# 检查键是否存在
if config.has_option('database', 'host'):
print("host键存在")
# 检查值
if config.has_section('server'):
print("server节存在")2.3 INI文件的高级用法
使用插值
import configparser
config = configparser.ConfigParser()
config['paths'] = {
'base_dir': '/home/user',
'data_dir': '%(base_dir)s/data',
'log_dir': '%(base_dir)s/logs'
}
# 获取值时会自动插值
data_dir = config.get('paths', 'data_dir')
print(data_dir) # /home/user/data处理特殊字符
import configparser
config = configparser.ConfigParser()
# 包含特殊字符的值需要转义
config['special'] = {
'path': 'C:\\Program Files\\MyApp',
'comment': 'This is a # comment'
}三、JSON配置文件
3.1 JSON文件格式
JSON是一种轻量级的数据交换格式,支持对象、数组、字符串、数字、布尔值和null。
{
"database": {
"host": "localhost",
"port": 3306,
"username": "admin",
"password": "secret"
},
"server": {
"host": "0.0.0.0",
"port": 8080,
"ssl": true
},
"debug": true,
"log_level": "INFO"
}3.2 json模块
json是Python标准库中处理JSON数据的模块。
创建JSON配置文件
import json
# 创建配置字典
config = {
"database": {
"host": "localhost",
"port": 3306,
"username": "admin",
"password": "secret"
},
"server": {
"host": "0.0.0.0",
"port": 8080,
"ssl": True
},
"debug": True,
"log_level": "INFO"
}
# 保存到文件
with open('config.json', 'w', encoding='utf-8') as f:
json.dump(config, f, indent=2, ensure_ascii=False)读取JSON配置文件
import json
# 读取配置文件
with open('config.json', 'r', encoding='utf-8') as f:
config = json.load(f)
# 访问配置
db_host = config['database']['host']
db_port = config['database']['port']
debug = config['debug']
print(f"Database Host: {db_host}")
print(f"Database Port: {db_port}")
print(f"Debug: {debug}")修改JSON配置文件
import json
# 读取配置
with open('config.json', 'r', encoding='utf-8') as f:
config = json.load(f)
# 修改配置
config['database']['host'] = '192.168.1.100'
config['database']['port'] = 5432
config['server']['ssl'] = False
# 保存配置
with open('config.json', 'w', encoding='utf-8') as f:
json.dump(config, f, indent=2, ensure_ascii=False)JSON的高级用法
import json
# 自定义编码器
class CustomEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, set):
return list(obj)
return super().default(obj)
# 使用自定义编码器
config = {
"tags": {"python", "json", "config"}
}
with open('config.json', 'w', encoding='utf-8') as f:
json.dump(config, f, indent=2, cls=CustomEncoder, ensure_ascii=False)四、YAML配置文件
4.1 YAML文件格式
YAML是一种人类可读的数据序列化格式,支持注释和复杂的数据结构。
database:
host: localhost
port: 3306
username: admin
password: secret
server:
host: 0.0.0.0
port: 8080
ssl: true
debug: true
log_level: INFO
# 注释
features:
- feature1
- feature2
- feature34.2 PyYAML模块
PyYAML是处理YAML文件的第三方库,需要安装:pip install pyyaml
创建YAML配置文件
import yaml
# 创建配置字典
config = {
'database': {
'host': 'localhost',
'port': 3306,
'username': 'admin',
'password': 'secret'
},
'server': {
'host': '0.0.0.0',
'port': 8080,
'ssl': True
},
'debug': True,
'log_level': 'INFO'
}
# 保存到文件
with open('config.yaml', 'w', encoding='utf-8') as f:
yaml.dump(config, f, default_flow_style=False, allow_unicode=True)读取YAML配置文件
import yaml
# 读取配置文件
with open('config.yaml', 'r', encoding='utf-8') as f:
config = yaml.safe_load(f)
# 访问配置
db_host = config['database']['host']
db_port = config['database']['port']
debug = config['debug']
print(f"Database Host: {db_host}")
print(f"Database Port: {db_port}")
print(f"Debug: {debug}")修改YAML配置文件
import yaml
# 读取配置
with open('config.yaml', 'r', encoding='utf-8') as f:
config = yaml.safe_load(f)
# 修改配置
config['database']['host'] = '192.168.1.100'
config['database']['port'] = 5432
# 保存配置
with open('config.yaml', 'w', encoding='utf-8') as f:
yaml.dump(config, f, default_flow_style=False, allow_unicode=True)YAML的高级用法
import yaml
# 处理多文档YAML
documents = """
---
database:
host: localhost
port: 3306
---
server:
host: 0.0.0.0
port: 8080
"""
# 加载所有文档
configs = list(yaml.safe_load_all(documents))
for config in configs:
print(config)五、TOML配置文件
5.1 TOML文件格式
TOML是一种简洁的配置文件格式,Python 3.11+内置支持。
[database]
host = "localhost"
port = 3306
username = "admin"
password = "secret"
[server]
host = "0.0.0.0"
port = 8080
ssl = true
debug = true
log_level = "INFO"
[[features]]
name = "feature1"
enabled = true
[[features]]
name = "feature2"
enabled = false5.2 tomllib模块(Python 3.11+)
tomllib是Python 3.11+内置的TOML读取模块。
读取TOML配置文件
import tomllib
# 读取配置文件
with open('config.toml', 'rb') as f:
config = tomllib.load(f)
# 访问配置
db_host = config['database']['host']
db_port = config['database']['port']
debug = config['debug']
print(f"Database Host: {db_host}")
print(f"Database Port: {db_port}")
print(f"Debug: {debug}")5.3 tomli_w模块(写入TOML)
tomli_w是第三方库,用于写入TOML文件:pip install tomli-w
创建TOML配置文件
import tomli_w
# 创建配置字典
config = {
'database': {
'host': 'localhost',
'port': 3306,
'username': 'admin',
'password': 'secret'
},
'server': {
'host': '0.0.0.0',
'port': 8080,
'ssl': True
},
'debug': True,
'log_level': 'INFO'
}
# 保存到文件
with open('config.toml', 'wb') as f:
tomli_w.dump(config, f)六、配置文件的实际应用
6.1 创建配置管理类
import configparser
import json
from pathlib import Path
class ConfigManager:
def __init__(self, config_file):
self.config_file = Path(config_file)
self.config = None
self.load()
def load(self):
"""加载配置文件"""
if not self.config_file.exists():
raise FileNotFoundError(f"配置文件不存在: {self.config_file}")
ext = self.config_file.suffix.lower()
if ext == '.ini':
self.config = configparser.ConfigParser()
self.config.read(self.config_file)
elif ext == '.json':
with open(self.config_file, 'r', encoding='utf-8') as f:
self.config = json.load(f)
else:
raise ValueError(f"不支持的配置文件格式: {ext}")
def save(self):
"""保存配置文件"""
ext = self.config_file.suffix.lower()
if ext == '.ini':
with open(self.config_file, 'w') as f:
self.config.write(f)
elif ext == '.json':
with open(self.config_file, 'w', encoding='utf-8') as f:
json.dump(self.config, f, indent=2, ensure_ascii=False)
def get(self, key, default=None):
"""获取配置值"""
if isinstance(self.config, configparser.ConfigParser):
section, option = key.split('.')
return self.config.get(section, option, fallback=default)
else:
keys = key.split('.')
value = self.config
for k in keys:
value = value.get(k)
if value is None:
return default
return value
def set(self, key, value):
"""设置配置值"""
if isinstance(self.config, configparser.ConfigParser):
section, option = key.split('.')
if not self.config.has_section(section):
self.config.add_section(section)
self.config.set(section, option, str(value))
else:
keys = key.split('.')
config = self.config
for k in keys[:-1]:
if k not in config:
config[k] = {}
config = config[k]
config[keys[-1]] = value
# 使用
config = ConfigManager('config.json')
db_host = config.get('database.host')
print(f"Database Host: {db_host}")6.2 环境变量配置
import os
from pathlib import Path
class EnvConfig:
"""环境变量配置"""
def __init__(self):
self.load_env_file()
def load_env_file(self):
"""加载.env文件"""
env_file = Path('.env')
if env_file.exists():
with open(env_file, 'r', encoding='utf-8') as f:
for line in f:
line = line.strip()
if line and not line.startswith('#'):
key, value = line.split('=', 1)
os.environ[key.strip()] = value.strip()
def get(self, key, default=None):
"""获取环境变量"""
return os.environ.get(key, default)
def get_int(self, key, default=0):
"""获取整数环境变量"""
value = self.get(key)
return int(value) if value else default
def get_bool(self, key, default=False):
"""获取布尔环境变量"""
value = self.get(key)
return value.lower() in ('true', '1', 'yes') if value else default
# 使用
env = EnvConfig()
db_host = env.get('DB_HOST', 'localhost')
db_port = env.get_int('DB_PORT', 3306)
debug = env.get_bool('DEBUG', False)
print(f"Database Host: {db_host}")
print(f"Database Port: {db_port}")
print(f"Debug: {debug}")6.3 配置文件验证
import json
from pathlib import Path
class ConfigValidator:
"""配置文件验证器"""
def __init__(self, schema):
self.schema = schema
def validate(self, config):
"""验证配置"""
errors = []
self._validate_dict(config, self.schema, '', errors)
return errors
def _validate_dict(self, config, schema, path, errors):
"""验证字典"""
for key, value_schema in schema.items():
if key not in config:
if value_schema.get('required', False):
errors.append(f"{path}.{key} is required")
continue
value = config[key]
expected_type = value_schema.get('type')
if expected_type and not isinstance(value, expected_type):
errors.append(f"{path}.{key} should be {expected_type.__name__}")
if 'values' in value_schema and value not in value_schema['values']:
errors.append(f"{path}.{key} should be one of {value_schema['values']}")
if 'min' in value_schema and value < value_schema['min']:
errors.append(f"{path}.{key} should be >= {value_schema['min']}")
if 'max' in value_schema and value > value_schema['max']:
errors.append(f"{path}.{key} should be <= {value_schema['max']}")
# 使用
schema = {
'database': {
'type': dict,
'required': True
},
'server': {
'type': dict,
'required': True
},
'debug': {
'type': bool,
'required': False
}
}
validator = ConfigValidator(schema)
with open('config.json', 'r', encoding='utf-8') as f:
config = json.load(f)
errors = validator.validate(config)
if errors:
print("配置验证失败:")
for error in errors:
print(f" - {error}")
else:
print("配置验证通过")6.4 配置文件热重载
import json
import time
from pathlib import Path
class HotReloadConfig:
"""支持热重载的配置"""
def __init__(self, config_file):
self.config_file = Path(config_file)
self.config = None
self.last_modified = 0
self.load()
def load(self):
"""加载配置文件"""
with open(self.config_file, 'r', encoding='utf-8') as f:
self.config = json.load(f)
self.last_modified = self.config_file.stat().st_mtime
def check_reload(self):
"""检查是否需要重新加载"""
current_modified = self.config_file.stat().st_mtime
if current_modified > self.last_modified:
print("配置文件已修改,重新加载...")
self.load()
return True
return False
def get(self, key, default=None):
"""获取配置值"""
self.check_reload()
keys = key.split('.')
value = self.config
for k in keys:
value = value.get(k)
if value is None:
return default
return value
# 使用
config = HotReloadConfig('config.json')
while True:
db_host = config.get('database.host')
print(f"Database Host: {db_host}")
time.sleep(5)七、配置文件的最佳实践
7.1 选择合适的配置文件格式
- INI:简单的键值对配置
- JSON:需要复杂数据结构
- YAML:需要人类可读和注释
- TOML:Python项目配置
7.2 配置文件分层
- 默认配置:内置在代码中
- 系统配置:系统级别的配置文件
- 用户配置:用户目录下的配置文件
- 环境变量:运行时覆盖
7.3 敏感信息处理
import os
from pathlib import Path
# 不要将敏感信息存储在配置文件中
# 使用环境变量或密钥管理服务
db_password = os.environ.get('DB_PASSWORD')
if not db_password:
raise ValueError("DB_PASSWORD环境变量未设置")7.4 配置文件验证
# 总是验证配置文件
errors = validator.validate(config)
if errors:
raise ValueError(f"配置验证失败: {errors}")7.5 配置文件备份
import shutil
from pathlib import Path
# 修改配置前备份
config_file = Path('config.json')
backup_file = Path('config.json.backup')
shutil.copy2(config_file, backup_file)八、常见问题与解决方案
8.1 配置文件不存在
问题:配置文件不存在导致程序崩溃
解决方案:
from pathlib import Path
config_file = Path('config.json')
if not config_file.exists():
# 创建默认配置
default_config = {
'database': {
'host': 'localhost',
'port': 3306
}
}
with open(config_file, 'w', encoding='utf-8') as f:
json.dump(default_config, f, indent=2)8.2 配置文件格式错误
问题:配置文件格式不正确
解决方案:
import json
try:
with open('config.json', 'r', encoding='utf-8') as f:
config = json.load(f)
except json.JSONDecodeError as e:
print(f"配置文件格式错误: {e}")
# 使用默认配置
config = get_default_config()8.3 配置项缺失
问题:配置文件中缺少必要的配置项
解决方案:
def get_config_with_defaults(config):
"""使用默认值填充缺失的配置"""
defaults = {
'database': {
'host': 'localhost',
'port': 3306
},
'debug': False
}
# 合并配置
return {**defaults, **config}8.4 配置文件编码问题
问题:配置文件编码不正确
解决方案:
# 总是指定编码
with open('config.json', 'r', encoding='utf-8') as f:
config = json.load(f)8.5 配置文件权限问题
问题:没有权限读取或写入配置文件
解决方案:
import os
config_file = 'config.json'
if not os.access(config_file, os.R_OK):
print("没有读取配置文件的权限")
if not os.access(config_file, os.W_OK):
print("没有写入配置文件的权限")九、总结
配置文件操作是Python编程中的重要技能。本集我们学习了:
- INI配置文件:使用configparser模块处理
- JSON配置文件:使用json模块处理
- YAML配置文件:使用PyYAML处理
- TOML配置文件:使用tomllib和tomli_w处理
- 实际应用:配置管理类、环境变量配置、配置验证、热重载
- 最佳实践:选择合适格式、分层配置、敏感信息处理
掌握这些技能将帮助你有效地管理应用程序配置,提高代码的可维护性和灵活性。
十、练习题
- 编写一个函数,创建INI配置文件并保存
- 实现一个配置管理类,支持JSON和INI格式
- 编写代码,验证配置文件的完整性
- 实现一个支持热重载的配置管理器
- 编写一个函数,将INI配置转换为JSON配置
- 实现环境变量配置管理器
- 编写代码,备份和恢复配置文件
- 实现配置文件的加密和解密功能