春江暮客

春江暮客的个人学习分享网站

Python 环境变量配置:严格解析布尔值、空值和整数

2026-10-25 技术
Python 环境变量配置:严格解析布尔值、空值和整数

定时任务里设置了 TASK_DRY_RUN=false,Python 却仍进入预览分支;批量大小留空后又在任务运行一半时才报错。环境变量传进来的是字符串,必须在任务入口明确转换并校验。

本文面向初学者到中级读者,用标准库写一个配置检查工具,区分未设置、空字符串和非法值,并加入命令行覆盖。工具输出最终配置,方便在真正的批处理开始前检查;它本身不执行批处理或修改数据文件。

1. 方法:先定义环境变量的规则

Python 的 os.environ 文档说明,环境变量的键和值都是字符串。bool("false") 和 bool("0") 都是 True,因为非空字符串为真;这遵循真值测试规则,并不会解析单词含义。

本文约定下面的规则。这些取值是工具自己的配置协议:

参数 未设置时 接受的已设置值
TASK_DRY_RUN True true、false、1、0,忽略大小写及首尾空白
TASK_BATCH_SIZE 100 去掉首尾空白后,必须为 1–4 位 ASCII 数字,数值为 1–1000
  1. 未设置才采用默认值;空字符串和纯空白都报错,避免部署时漏填被悄悄忽略。
  2. 先验证这两个环境变量,再应用命令行覆盖。即使传入覆盖参数,已有的非法环境值仍会导致失败。
  3. 有效配置的优先级为:默认值 < 环境变量 < 显式命令行参数。

例如 0001 接受为整数 1,但 +1、1_000、小数和全角数字不接受。工具只读取表中的两个变量,不会自动加载 .env 文件,也不会检查其他变量的拼写。

2. 准备环境

需要已有的 Python 3.9 或更新版本,以及 Linux 下的 bash 或 zsh;无需安装第三方包。本文在 Python 3.11.4 上验证。

  1. 新建演示目录:
python3 --version
mkdir env-settings-demo
cd env-settings-demo
  1. 将下一节完整代码保存为 env_settings.py。如果只在定时任务里失败,先对照Python 定时任务环境排查确认启动入口和解释器。

3. 实现:一次读取、严格转换

"""Validate task environment settings and print JSON. Python 3.9+."""
import argparse
import json
import os
import re


def parse_bool(value):
    token = value.casefold()
    if token in ("true", "1"):
        return True
    if token in ("false", "0"):
        return False
    raise ValueError("expected true, false, 1, or 0")


def parse_batch(value):
    message = "expected 1..1000 using 1..4 ASCII digits"
    if not re.fullmatch(r"[0-9]{1,4}", value):
        raise ValueError(message)
    number = int(value)
    if not 1 <= number <= 1000:
        raise ValueError(message)
    return number


def env_value(name, default, convert):
    raw = os.environ.get(name)
    if raw is None:
        return default
    value = raw.strip()
    if not value:
        raise ValueError(f"{name}: empty value; unset it to use the default")
    try:
        return convert(value)
    except ValueError as exc:
        raise ValueError(f"{name}: {exc}") from exc


def main():
    parser = argparse.ArgumentParser(description=__doc__, allow_abbrev=False)
    parser.add_argument(
        "--dry-run", action=argparse.BooleanOptionalAction, default=None,
        help="override TASK_DRY_RUN; defaults to true when unset",
    )
    parser.add_argument("--batch-size", help="override TASK_BATCH_SIZE (1..1000)")
    args = parser.parse_args()
    try:
        settings = {
            "dry_run": env_value("TASK_DRY_RUN", True, parse_bool),
            "batch_size": env_value("TASK_BATCH_SIZE", 100, parse_batch),
        }
        if args.dry_run is not None:
            settings["dry_run"] = args.dry_run
        if args.batch_size is not None:
            settings["batch_size"] = parse_batch(args.batch_size.strip())
    except ValueError as exc:
        parser.error(str(exc))
    print(json.dumps(settings, sort_keys=True))


if __name__ == "__main__":
    main()

raw is None 区分未设置和空值,不能写成 os.environ.get(name) or default,否则空字符串会被当作默认值。整数转换先限制字符和长度,再检查范围,因此很长的数字也会得到固定的错误提示。

命令行使用 BooleanOptionalAction,生成 --dry-run 与 --no-dry-run。默认值设为 None,才能识别用户是否明确传入开关,不能用 if args.dry_run 判断是否覆盖。不要把布尔参数声明成 type=bool。

工具只输出这两个经过转换的字段,不打印整个进程环境。接入自己的批处理时,把任务调用放在配置验证成功之后,并在业务代码中实际处理 dry_run;参数名称本身不会阻止写操作。

4. 使用:默认值、环境变量和覆盖

  1. 查看帮助,再用不含这两个变量的子进程验证默认值:
python3 env_settings.py --help
env -u TASK_DRY_RUN -u TASK_BATCH_SIZE python3 env_settings.py

默认输出:

{"batch_size": 100, "dry_run": true}

env -u 只从本次子进程环境中移除指定变量,不改变当前终端。这样不会被先前导出的变量干扰。

  1. 显式设置两个环境变量;下面的赋值只用于这一次命令:
TASK_DRY_RUN=false TASK_BATCH_SIZE=25 python3 env_settings.py

输出:

{"batch_size": 25, "dry_run": false}
  1. 用命令行覆盖有效环境值:
TASK_DRY_RUN=false TASK_BATCH_SIZE=25 \
  python3 env_settings.py --dry-run --batch-size 7

输出:

{"batch_size": 7, "dry_run": true}
  1. 检查空值与越界。每次运行后立即查看退出码;这两条命令都应该失败:
TASK_DRY_RUN='' TASK_BATCH_SIZE=25 python3 env_settings.py
printf 'exit=%s\n' "$?"
TASK_DRY_RUN=true TASK_BATCH_SIZE=1001 python3 env_settings.py
printf 'exit=%s\n' "$?"

两次退出码均为 2,标准错误分别包含 TASK_DRY_RUN: empty value 和 TASK_BATCH_SIZE: expected 1..1000,标准输出没有配置 JSON。成功时退出码为 0。不要用 || true 掩盖启动配置错误。

5. 验证:在独立子进程中检查边界

把下面保存为同目录的 check_env_settings.py,执行 python3 check_env_settings.py。它从子进程环境中移除已有的两个变量,因此不受终端配置影响,也不会修改当前进程之外的环境。

import json
import os
from pathlib import Path
import subprocess
import sys

script = Path(__file__).with_name("env_settings.py")
base = dict(os.environ)
for name in ("TASK_DRY_RUN", "TASK_BATCH_SIZE"):
    base.pop(name, None)


def run(values, args=()):
    return subprocess.run(
        [sys.executable, str(script), *args],
        env={**base, **values}, capture_output=True, text=True, timeout=5,
    )


valid = [
    ({}, (), {"batch_size": 100, "dry_run": True}),
    ({"TASK_DRY_RUN": "false", "TASK_BATCH_SIZE": "25"}, (),
     {"batch_size": 25, "dry_run": False}),
    ({"TASK_DRY_RUN": " TrUe ", "TASK_BATCH_SIZE": " 0001 "}, (),
     {"batch_size": 1, "dry_run": True}),
    ({"TASK_DRY_RUN": "0", "TASK_BATCH_SIZE": "1000"}, ("--dry-run",),
     {"batch_size": 1000, "dry_run": True}),
    ({"TASK_DRY_RUN": "1"}, ("--no-dry-run", "--batch-size", "7"),
     {"batch_size": 7, "dry_run": False}),
]
for values, args, expected in valid:
    result = run(values, args)
    assert result.returncode == 0, result.stderr
    payload = json.loads(result.stdout)
    assert payload == expected
    assert type(payload["dry_run"]) is bool
    assert type(payload["batch_size"]) is int
    assert result.stderr == ""

invalid = [
    ("TASK_DRY_RUN", ""), ("TASK_DRY_RUN", " "),
    ("TASK_DRY_RUN", "flase"), ("TASK_DRY_RUN", "yes"),
    ("TASK_BATCH_SIZE", ""), ("TASK_BATCH_SIZE", "0"),
    ("TASK_BATCH_SIZE", "1001"), ("TASK_BATCH_SIZE", "-1"),
    ("TASK_BATCH_SIZE", "2.5"), ("TASK_BATCH_SIZE", "1_000"),
    ("TASK_BATCH_SIZE", "12"), ("TASK_BATCH_SIZE", "9" * 5000),
]
for name, value in invalid:
    result = run({name: value})
    assert result.returncode == 2
    assert result.stdout == ""
    assert name in result.stderr

result = run({"TASK_DRY_RUN": "flase"}, ("--dry-run",))
assert result.returncode == 2 and result.stdout == ""
assert "TASK_DRY_RUN" in result.stderr
result = run({}, ("--batch-size", "0"))
assert result.returncode == 2 and result.stdout == ""
result = run({"TASK_DRY_RUN": "flase"}, ("--help",))
assert result.returncode == 0 and "--no-dry-run" in result.stdout
print("OK: defaults, booleans, boundaries, overrides, and errors")

预期输出:

OK: defaults, booleans, boundaries, overrides, and errors

测试同时核对返回码、JSON 的实际布尔类型、标准输出和标准错误,覆盖空值、拼写错误、数字边界、超长数字、显式关闭开关,以及非法环境值不能被覆盖隐藏的策略。--help 在环境值非法时也应正常工作,便于排查。

6. 常见错误与修复

现象 原因与处理
false 仍被当作真 删除 bool(raw) 或 type=bool,改用明确的允许值转换。
empty value 变量已存在但没有内容。填入有效值;需要默认值时,用 unset TASK_DRY_RUN 或 unset TASK_BATCH_SIZE 移除对应变量。
expected true, false, 1, or 0 检查启动命令中的拼写;本文不接受 yes、off 等其他写法。
expected 1..1000 去掉符号、分隔符和小数,填写范围内的 ASCII 十进制整数。
改了终端变量,已运行的任务没有变化 在任务启动入口设置变量并重新启动进程;另一个终端的修改不会更新已有进程环境。
.env 文件写好了却没生效 本工具没有 .env 加载器。应由启动入口明确传入这两个环境变量。

参数增多、需要嵌套结构时,可以转用TOML 任务配置教程;不要在环境变量里自行设计复杂的分隔格式。若配置校验后仍遇到中文日志编码错误,继续检查标准输出编码。

7. 总结

在任务启动时区分未设置、空值和非法字符串,再统一转换布尔值与整数,可以让配置问题尽早暴露。为环境变量和命令行写清优先级,并用独立子进程检查退出码与实际输出。把通过校验的设置交给业务代码后,再开始真正的批处理。

标签

1024 12306 ablang adsense agents.md ai ai-agent ai-agents ai-seo algorithm amp antibodies apparmor argparse automation backup bash batch-processing binarycif bioinformatics biopython blockchain boltz bootstrapping boxes bubblewrap c-index cca cdn chatgpt checkpoint cli cloudflare codex cofoldarena concurrency configuration copy cpu监控 cron csv cuda curl data-leakage data-processing data-quality data-validation datascience datavisualization datetime debugging decimal deployment desktop-app devtools disk-space disown dns docker dovecot download electron encoding esm esm2 esm3 esmc esmfold2 faceswap fasta fastmcp faulthandler ffmpeg file-descriptors file-io flashppi flask folium frontend game generator git github-actions google google-research grep gzip harness hls html http hugo imports indexnow javascript jev json jsonl just k-means kaggle langfuse leecode linux list litellm llm llms.txt logging logs lollipop lsof m3u8 machine-learning macos manacher matplotlib mcp memory mirror mmcif model-evaluation monitoring mp3 mp4 mpnn multiomics mutation mysql nanobert nanobodies networkx nginx normalize numpy ollama omegatherm pandas password pathlib pdb pep-723 phaser pillow pip postfix preprocessing print protein-alignment protein-design protein-embeddings protein-interactions protein-language-models protein-stability protein-structure proxy pydantic pyecharts pyqt python python3 r raincloud reproducibility requests reservoir-sampling rfoptimization rg ripgrep rosettafold3 roundcube rrsi rsi rsync s-tui sampling scale scikit-learn scrapy screen seaborn security selenium seo sequence-identity sha256 shell signals sklearn socket solana somaticsignatures spl sqlite ssh standardize static-site subprocess symlink sysadmin system-one tarfile tcp tensorflow tkinter tls toml tracemalloc tron tronpy troubleshooting turtle typesafe-ai ubuntu unicode urllib usdt utf-8 uv vhhbert vite webp wordcloud wordpress workflow yaml zipfile zoneinfo 后台 寓言 概率 经济 贸易 迅雷解析 钱包

友情链接

其它