第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 = value6

2.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
  - feature3

4.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 = false

5.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编程中的重要技能。本集我们学习了:

  1. INI配置文件:使用configparser模块处理
  2. JSON配置文件:使用json模块处理
  3. YAML配置文件:使用PyYAML处理
  4. TOML配置文件:使用tomllib和tomli_w处理
  5. 实际应用:配置管理类、环境变量配置、配置验证、热重载
  6. 最佳实践:选择合适格式、分层配置、敏感信息处理

掌握这些技能将帮助你有效地管理应用程序配置,提高代码的可维护性和灵活性。

十、练习题

  1. 编写一个函数,创建INI配置文件并保存
  2. 实现一个配置管理类,支持JSON和INI格式
  3. 编写代码,验证配置文件的完整性
  4. 实现一个支持热重载的配置管理器
  5. 编写一个函数,将INI配置转换为JSON配置
  6. 实现环境变量配置管理器
  7. 编写代码,备份和恢复配置文件
  8. 实现配置文件的加密和解密功能
« 上一篇 文件压缩与解压 下一篇 » os模块详解