三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

Python脚本执行全解析:从基础运行到工程化部署的完整指南

Python脚本执行全解析:从基础运行到工程化部署的完整指南

1. 从“双击运行”到“专业执行”:一个Python文件的生命周期

如果你刚接触Python,可能觉得运行一个.py文件无非就是双击它,或者在命令行里敲个python xxx.py。这没错,但这只是冰山一角。作为一个写了十几年Python的老码农,我见过太多因为对“运行”这件事理解不到位而踩的坑:脚本在A的电脑上跑得好好的,到B那儿就报错;打包成exe后数据文件找不到了;想定时执行脚本却总是不成功。今天,我们就来彻底拆解“运行一个py文件”这件事,它远不止一个命令那么简单,而是一个涉及环境、路径、参数传递和生命周期管理的系统工程。

“运行py文件”的核心,是让Python解释器读取你写的文本代码,将其编译成字节码,然后在特定的上下文中执行这些指令。这个过程看似简单,但其中每一个环节——从你敲下回车前环境变量的准备,到脚本结束后资源的释放——都藏着细节。无论是想给脚本传参、定时运行画个折线图,还是最终封装成独立的exe,你都需要理解这背后的完整链条。本文将从最基础的命令行执行讲起,逐步深入到多脚本协作、打包部署等实战场景,并分享那些官方文档里不会写的、我踩过无数坑才总结出的经验。

2. 基石:理解Python脚本的执行环境与入口

在你运行任何脚本之前,系统需要知道三件事:Python解释器在哪、脚本文件在哪、以及执行时的工作目录是什么。这三者共同构成了脚本的运行环境,环境不对,一切白费。

2.1 Python解释器的定位:不只是python命令

当你输入python script.py时,操作系统会去PATH环境变量列出的路径里寻找名为python的可执行文件。这里第一个坑就来了:你的系统里可能有多个Python。比如通过官网安装的Python 3.12、Anaconda自带的Python、或者系统自带的Python 2.7(在一些老Linux系统上)。

如何确认当前使用的是哪个Python?在命令行中执行以下命令可以看清全貌:

# 查看当前python命令指向的完整路径 which python # 在Linux/macOS上 where python # 在Windows的cmd上 Get-Command python # 在Windows PowerShell上 # 查看该Python的详细版本信息 python --version python -c "import sys; print(sys.executable, sys.version)"

我强烈建议在脚本开头通过sys.executable打印出解释器路径,这在排查“为什么在他机器上运行结果不一样”的问题时非常有用。

对于需要特定版本Python的项目,最佳实践是使用虚拟环境(Virtual Environment)。这并非小题大做,而是保证环境纯净、依赖隔离的工业级标准。使用venv模块创建:

# 创建虚拟环境,环境目录名为`.venv` python -m venv .venv # 激活虚拟环境 # Windows (.venv\Scripts\activate) # Linux/macOS (source .venv/bin/activate) # 激活后,命令行提示符通常会变化,且`python`命令将明确指向该环境下的解释器

在虚拟环境中安装的所有包(如pandas,requests)都只存在于该环境中,不会影响系统全局的Python,完美解决了版本冲突问题。

2.2 工作目录与文件路径:相对与绝对的陷阱

这是新手最容易栽跟头的地方。一个经典的错误是:脚本里用open('data.csv')读取同目录下的data.csv文件,在IDE里运行正常,但通过命令行或任务计划程序运行时却报FileNotFoundError

根因在于“当前工作目录”(Current Working Directory, CWD)的变化。Python的open()函数在不指定绝对路径时,使用的是相对于当前工作目录的路径。当你从IDE运行时,IDE通常将项目根目录或脚本所在目录设为工作目录。而当你从命令行运行python /path/to/script.py时,工作目录是你执行命令时所在的目录,而非脚本所在目录。

解决方案是永远不要对数据文件、配置文件使用裸露的相对路径。应该使用基于脚本文件自身位置的绝对路径。__file__这个内置变量是你的好帮手,它保存了当前脚本文件的路径。

import os import sys # 获取当前脚本文件的绝对路径 script_path = os.path.abspath(__file__) # 获取脚本文件所在的目录 script_dir = os.path.dirname(script_path) # 构建数据文件的绝对路径 data_file_path = os.path.join(script_dir, 'data', 'config.json') # 现在,无论从何处运行脚本,都能准确定位到文件 with open(data_file_path, 'r') as f: config = json.load(f)

注意:在某些特殊执行方式(如冻结打包成exe后)下,__file__的行为可能不同。更健壮的方法是使用sys._MEIPASS(PyInstaller打包后)或pkgutil等模块,这在后续打包章节会详细说明。

2.3 脚本的入口:if __name__ == "__main__":的真正意义

你可能在很多脚本末尾看到过这段代码:

def main(): # 你的主要逻辑 pass if __name__ == "__main__": main()

它的作用是什么?简单说,它定义了脚本的“入口点”,并区分了“被直接运行”和“被作为模块导入”两种场景。

__name__是一个内置变量。当一个Python文件被直接运行时(例如python my_script.py),__name__的值会被设置为"__main__"。如果这个文件被另一个文件通过import my_script导入,那么__name__的值就是模块名"my_script"

这个机制带来了两大好处:

  1. 模块化与复用:你可以将一个大脚本中的函数和类写在if __name__ == "__main__":之外。这样,其他脚本就可以安全地导入这个文件,使用其中的功能,而不会触发它原本作为独立脚本时的执行逻辑(比如启动一个GUI界面)。
  2. 可测试性:你可以方便地为main()函数编写单元测试,因为导入时不会执行它。

一个更工程化的入口点写法示例:

#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 数据清洗脚本:用于处理每日的销售数据CSV文件。 用法: python data_cleaner.py --input daily_sales.csv --output cleaned/ """ import argparse import sys import pandas as pd from pathlib import Path def clean_data(input_path: Path, output_dir: Path) -> None: """核心清洗逻辑""" # ... 具体的清洗步骤 ... print(f"数据清洗完成,结果已保存至 {output_dir}") def parse_arguments(): """解析命令行参数""" parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--input', required=True, help='输入CSV文件路径') parser.add_argument('--output', default='./cleaned', help='输出目录') return parser.parse_args() def main(): """脚本主入口""" args = parse_arguments() input_path = Path(args.input) output_dir = Path(args.output) if not input_path.exists(): print(f"错误:输入文件不存在 {input_path}", file=sys.stderr) sys.exit(1) output_dir.mkdir(parents=True, exist_ok=True) try: clean_data(input_path, output_dir) except Exception as e: print(f"清洗过程中发生错误: {e}", file=sys.stderr) sys.exit(1) if __name__ == "__main__": main()

这样的结构清晰、健壮,并且自带文档和参数解析,是生产级脚本应有的样子。

3. 进阶执行:参数传递、模块化与定时任务

掌握了基础执行后,你会发现单机、单次运行往往不能满足需求。脚本需要接收外部指令,需要与其他脚本协作,甚至需要自动定时运行。

3.1 向Python脚本传递参数的四种主流方式

这是实现脚本灵活性的关键。根据热词“python给另一个py脚本传递参数”,我们来详细对比。

1. 命令行参数(最常用、最标准)使用内置的argparse库是专业选择。它功能强大,能自动生成帮助信息,支持位置参数、可选参数、类型检查、默认值等。

import argparse parser = argparse.ArgumentParser(description='绘制折线图') parser.add_argument('--data-file', '-d', required=True, help='数据文件路径') parser.add_argument('--output', '-o', default='chart.png', help='输出图片名称') parser.add_argument('--interval', '-i', type=int, default=3600, help='数据点时间间隔(秒),用于“python每隔一段时间画折线图”的场景') parser.add_argument('--title', help='图表标题') args = parser.parse_args() print(f"将处理文件: {args.data_file}") print(f"时间间隔为: {args.interval}秒") # 之后就可以用 args.data_file, args.interval 等变量了

运行示例:python plotter.py -d sales.csv -i 1800 --title "每小时销售额"

2. 系统环境变量适用于配置一些不常改变、或涉及敏感信息(如API密钥、数据库密码)的参数。可以通过操作系统的环境变量设置,或在运行前临时设置。

# 在运行脚本前设置环境变量 export API_KEY="your_secret_key_here" # Linux/macOS set API_KEY=your_secret_key_here # Windows cmd $env:API_KEY="your_secret_key_here" # Windows PowerShell # 然后在Python脚本中读取 import os api_key = os.environ.get('API_KEY') if not api_key: raise ValueError("请设置 API_KEY 环境变量")

3. 配置文件(JSON, YAML, INI)当参数数量多、结构复杂时,配置文件比命令行更合适。例如,一个爬虫脚本的配置可能包含多个URL、请求头、解析规则等。

# config.json { "start_urls": ["https://example.com/page1", "https://example.com/page2"], "request_headers": {"User-Agent": "MyBot/1.0"}, "output_dir": "./data", "max_pages": 100 } # 在脚本中读取 import json with open('config.json', 'r') as f: config = json.load(f) for url in config['start_urls']: # 开始爬取...

4. 标准输入(stdin)适用于脚本作为管道的一部分,接收上一个命令的输出。这在处理流数据时非常有用。

# 将前一个命令的输出作为脚本输入 cat logfile.txt | grep "ERROR" | python error_analyzer.py # 在 error_analyzer.py 中 import sys for line in sys.stdin: # 逐行读取标准输入 process_error_line(line)

经验之谈:对于简单的、临时的参数,用sys.argv列表直接获取也行(sys.argv[0]是脚本名,sys.argv[1:]是参数)。但对于任何打算复用或分享的脚本,请务必使用argparse,它能极大提升脚本的易用性和专业性。

3.2 脚本间的调用与模块化:超越os.system

直接运行另一个脚本,除了在命令行手动敲,也可以在Python代码里完成。但方法有优劣。

不推荐:os.system()subprocess.call()(仅用于简单调用)

import os os.system('python another_script.py arg1 arg2') # 阻塞执行,获取返回值麻烦

这种方式相当于开了一个新的子进程,运行一个完全独立的Python解释器。两个脚本之间的内存空间是隔离的,无法直接共享变量。它适合调用完全独立的工具,但不适合有数据交互的协作。

推荐:将脚本作为模块导入(用于函数和类的复用)这是更优雅、更高效的方式。前提是目标脚本被设计为可导入的(即使用了if __name__ == "__main__":隔离了执行入口)。

# 假设有 data_processor.py,里面定义了 process_data(raw_data) 函数 import data_processor raw_data = load_my_data() cleaned_data = data_processor.process_data(raw_data) # 直接调用其函数

这种方式直接在当前进程内调用函数,没有启动新解释器的开销,并且可以方便地传递复杂的数据对象(如列表、字典、Pandas DataFrame)。

折中方案:subprocess.run()进行可控的进程调用当你确实需要运行一个独立进程,并需要捕获其输出、控制超时时,使用subprocess.run

import subprocess result = subprocess.run( ['python', 'another_script.py', '--input', 'data.csv'], capture_output=True, # 捕获标准输出和错误 text=True, # 以文本形式返回 timeout=30 # 设置超时(秒) ) if result.returncode == 0: print("子脚本运行成功,输出:", result.stdout) else: print("子脚本失败,错误:", result.stderr)

3.3 实现自动化:让Python脚本定时运行

“定时运行”是脚本价值倍增的关键。对于“python每隔一段时间画折线图”这类需求,有几种实现路径。

1. 脚本内循环 + 睡眠(适用于简单、长期的守护进程)在脚本内部使用while循环和time.sleep()

import time import schedule # 第三方库,提供更友好的定时语法 import plotter # 你之前写好的画图函数 def job(): print("开始执行画图任务...") plotter.draw_latest_chart() # 调用你的画图逻辑 print("任务完成。") # 使用schedule库,更清晰 schedule.every().day.at("10:30").do(job) schedule.every(2).hours.do(job) # 每两小时执行一次 while True: schedule.run_pending() time.sleep(60) # 每分钟检查一次是否有任务需要执行

这种方式简单,但脚本需要一直挂在后台运行,且一旦脚本崩溃或服务器重启,任务就停止了。

2. 操作系统级的任务计划程序(生产环境推荐)这是最可靠的方式。将脚本的执行交给操作系统的调度器。

  • Windows:使用“任务计划程序”。你可以设置触发器(每天、每周、启动时等),并指定操作(启动程序python.exe,参数为你的脚本路径)。
  • Linux/macOS:使用cron。通过crontab -e编辑定时任务。
    # 每天上午9点15分执行脚本 15 9 * * * /usr/bin/python3 /home/user/projects/my_plotter.py --interval 3600

    注意:在cron中,环境变量可能与你的登录Shell不同。务必在脚本中使用绝对路径,或者在cron任务中显式设置PATHPYTHONPATH

3. 使用专用的任务队列(用于分布式、复杂的作业调度)对于企业级应用,可以考虑使用Celery(搭配RedisRabbitMQ)或APScheduler。它们提供了更强大的功能,如任务重试、结果存储、分布式执行、任务依赖管理等。

选择建议:对于个人或简单的服务器任务,优先使用操作系统的任务计划程序(cron或Windows任务计划)。它稳定、资源消耗低、不依赖Python进程常驻。对于需要复杂调度逻辑或集成在Web应用中的任务,再考虑scheduleCelery

4. 从脚本到产品:打包、部署与问题排查

当你开发了一个有用的脚本,并希望分享给没有Python环境的人使用,或者部署到生产服务器时,打包就成了必经之路。热词中提到的“在线py生成exe网站”和“py封装exe后读取csv”都指向了这个需求。

4.1 本地打包:使用PyInstaller

“在线生成exe网站”存在安全风险(你的源代码需要上传),且功能受限。本地打包工具PyInstaller是社区标准选择。

基本使用:

# 安装 pip install pyinstaller # 最简打包(生成一个单独的exe,包含所有依赖) pyinstaller --onefile your_script.py # 打包后,exe文件会在 `dist` 目录下

--onefile参数将所有依赖打包进一个exe,方便分发,但启动速度稍慢。--onedir参数会生成一个目录,包含exe和依赖库,启动更快。

处理数据文件与路径问题(解决“py封装exe后读取csv”的痛点)这是打包中最常见的坑。你的脚本里用open('data.csv')pd.read_csv('config/config.json'),在开发时正常,但打包后这些文件被“冻结”进了exe,运行时找不到路径了。

PyInstaller的解决方案:使用sys._MEIPASS

  1. 修改你的代码,使其能适应开发环境和打包后环境。
    import os import sys def get_resource_path(relative_path): """ 获取资源的绝对路径。适用于开发环境和PyInstaller打包后环境。""" try: # PyInstaller创建临时文件夹,将资源存储于_MEIPASS中 base_path = sys._MEIPASS except AttributeError: # 正常开发环境,使用当前文件所在目录的绝对路径 base_path = os.path.abspath(".") return os.path.join(base_path, relative_path) # 使用示例 csv_path = get_resource_path('data/data.csv') df = pd.read_csv(csv_path)
  2. 告诉PyInstaller哪些文件需要被打包进去。创建一个.spec文件(首次运行pyinstaller会自动生成),或通过命令行参数指定。
    # 方法1:通过命令行添加数据文件 pyinstaller --onefile --add-data "data/data.csv;data" your_script.py # 格式为“源路径;目标路径(在exe中的相对路径)” # 方法2:编辑.spec文件(更推荐,便于管理复杂配置) # 在生成的 your_script.spec 文件中,修改 datas 列表: # datas=[('data/data.csv', 'data'), ('config.json', '.')],
    这样,打包时这些文件会被复制到临时目录(_MEIPASS指向的路径),你的代码通过get_resource_path就能正确找到它们。

4.2 依赖管理与环境冻结:requirements.txt

确保别人能正确安装你脚本所需的库,是协作和部署的基础。永远不要口头说“你需要安装pandas和requests”。

# 生成当前环境的所有依赖列表 pip freeze > requirements.txt # 在新环境中一键安装所有依赖 pip install -r requirements.txt

更专业的做法是使用pipenvpoetry,它们不仅能管理依赖,还能管理虚拟环境,并生成更精确的锁文件。

4.3 常见运行问题排查指南

即使一切就绪,运行脚本时仍可能遇到各种问题。以下是一个快速排查清单:

  1. ModuleNotFoundError: No module named 'xxx'

    • 检查:虚拟环境是否已激活?是否在正确的环境中执行pip install
    • 解决:激活虚拟环境,并运行pip install -r requirements.txt
  2. 脚本在IDE里能跑,命令行报错

    • 检查:工作目录(CWD)是否不同?导致相对路径失效。环境变量PYTHONPATH是否被IDE修改过?
    • 解决:使用基于__file__的绝对路径。在命令行中打印os.getcwd()sys.path进行对比。
  3. 打包后的exe文件巨大

    • 原因:PyInstaller打包了整个Anaconda环境或不必要的库。
    • 解决:在干净的虚拟环境中安装最小依赖集后再打包。使用--exclude-module排除不需要的模块。
  4. 脚本执行慢或内存占用高

    • 排查:使用cProfile模块进行性能分析。
      import cProfile cProfile.run('my_main_function()', 'profile_stats')
    • 分析:使用pstats模块或snakeviz可视化工具查看分析结果,找到性能瓶颈。
  5. “请安装缺失的包以使用此工作流”

    • 场景:常见于一些集成了Python的软件(如某些AI工具、插件系统)。
    • 解决:按照提示,首先激活或切换到该软件指定的Python环境,然后再运行pip install命令安装缺失的包。关键是要确保pip安装到了正确的Python解释器下,可以使用python -m pip install package_name来避免歧义。

5. 工程化实践:构建一个健壮的数据处理与可视化脚本

让我们综合运用以上所有知识,构建一个符合工程标准的脚本。这个脚本将模拟热词中提到的场景:定期(每隔一段时间)从某个数据源读取数据,进行处理,并绘制折线图。同时,它支持命令行参数,路径处理健壮,并且日志清晰。

项目结构:

data_plotter/ ├── src/ │ └── plotter.py # 主逻辑脚本 ├── data/ │ ├── input/ # 存放输入的CSV文件 │ └── output/ # 存放生成的图表 ├── config/ │ └── settings.yaml # 配置文件 ├── logs/ # 日志目录(自动创建) ├── requirements.txt # 依赖列表 └── README.md # 项目说明

src/plotter.py核心内容:

#!/usr/bin/env python3 """ 数据可视化脚本:定期读取CSV数据,合并处理,并生成折线图。 支持命令行参数和配置文件。 """ import argparse import logging import sys import time from datetime import datetime from pathlib import Path from typing import List, Optional import pandas as pd import matplotlib.pyplot as plt import yaml # 需要安装PyYAML: pip install PyYAML # --- 路径处理函数(兼容开发与打包) --- def get_base_path() -> Path: """返回项目基础路径。""" try: # 适用于PyInstaller打包 base = Path(sys._MEIPASS) except AttributeError: # 开发环境:假设脚本在 src/,项目根目录是其父目录的父目录 base = Path(__file__).parent.parent return base BASE_DIR = get_base_path() DATA_INPUT_DIR = BASE_DIR / "data" / "input" DATA_OUTPUT_DIR = BASE_DIR / "data" / "output" CONFIG_DIR = BASE_DIR / "config" LOG_DIR = BASE_DIR / "logs" # --- 日志配置 --- def setup_logging(log_level: str = "INFO") -> None: """配置日志记录。""" LOG_DIR.mkdir(parents=True, exist_ok=True) log_file = LOG_DIR / f"plotter_{datetime.now():%Y%m%d}.log" logging.basicConfig( level=getattr(logging, log_level.upper()), format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler(log_file, encoding='utf-8'), logging.StreamHandler(sys.stdout) # 同时输出到控制台 ] ) logging.info(f"日志初始化完成,文件位于: {log_file}") # --- 配置加载 --- def load_config(config_name: str = "settings.yaml") -> dict: """从YAML文件加载配置。""" config_path = CONFIG_DIR / config_name if not config_path.exists(): logging.warning(f"配置文件 {config_path} 不存在,使用默认配置。") return {"chart_style": "ggplot", "default_interval": 3600} with open(config_path, 'r', encoding='utf-8') as f: config = yaml.safe_load(f) logging.info(f"已加载配置文件: {config_path}") return config # --- 核心数据处理函数(模拟“py字典相同数据合并”) --- def merge_duplicate_data(dataframes: List[pd.DataFrame]) -> pd.DataFrame: """ 合并多个DataFrame,并对相同键(如‘date’)的数据进行聚合(如求和)。 这是一个简化示例,实际合并逻辑可能更复杂。 """ if not dataframes: return pd.DataFrame() # 假设所有DataFrame都有'date'和'value'列 combined_df = pd.concat(dataframes, ignore_index=True) # 按日期合并相同数据(求和) merged_df = combined_df.groupby('date', as_index=False)['value'].sum() logging.info(f"数据合并完成,共处理 {len(combined_df)} 行,合并为 {len(merged_df)} 个唯一日期。") return merged_df # --- 绘图函数 --- def plot_timeseries(data_df: pd.DataFrame, output_path: Path, title: str = "数据趋势图", interval_seconds: Optional[int] = None) -> None: """绘制时间序列折线图并保存。""" if data_df.empty: logging.error("数据为空,无法绘图。") return plt.style.use('ggplot') # 使用ggplot样式 fig, ax = plt.subplots(figsize=(12, 6)) # 确保日期列是datetime类型 data_df['date'] = pd.to_datetime(data_df['date']) data_df = data_df.sort_values('date') ax.plot(data_df['date'], data_df['value'], marker='o', linestyle='-', linewidth=2) ax.set_xlabel("日期", fontsize=12) ax.set_ylabel("数值", fontsize=12) ax.set_title(title, fontsize=14, fontweight='bold') # 如果指定了时间间隔,可以在x轴标注上体现 if interval_seconds: interval_hours = interval_seconds / 3600 ax.set_title(f"{title} (数据间隔: {interval_hours}小时)", fontsize=14) plt.xticks(rotation=45) plt.tight_layout() # 自动调整布局防止标签重叠 DATA_OUTPUT_DIR.mkdir(parents=True, exist_ok=True) plt.savefig(output_path, dpi=300) plt.close(fig) logging.info(f"图表已保存至: {output_path}") # --- 主逻辑 --- def main_process(input_pattern: str = "*.csv", interval_hours: int = 24) -> None: """主处理流程。""" start_time = time.time() logging.info("="*50) logging.info("开始执行数据可视化任务") # 1. 查找输入文件 input_files = list(DATA_INPUT_DIR.glob(input_pattern)) if not input_files: logging.warning(f"在 {DATA_INPUT_DIR} 中未找到匹配 {input_pattern} 的文件。") return logging.info(f"找到 {len(input_files)} 个数据文件: {[f.name for f in input_files]}") # 2. 读取并合并数据 dataframes = [] for file in input_files: try: df = pd.read_csv(file) # 简单的数据校验 required_cols = {'date', 'value'} if not required_cols.issubset(df.columns): logging.error(f"文件 {file.name} 缺少必要列 {required_cols},已跳过。") continue dataframes.append(df) logging.debug(f"已读取文件: {file.name},行数: {len(df)}") except Exception as e: logging.error(f"读取文件 {file.name} 时出错: {e}") merged_df = merge_duplicate_data(dataframes) if merged_df.empty: logging.error("无有效数据可处理,任务终止。") return # 3. 生成图表 timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") output_filename = f"timeseries_chart_{timestamp}.png" output_path = DATA_OUTPUT_DIR / output_filename chart_title = f"数据趋势图 (数据源: {len(dataframes)}个文件)" plot_timeseries(merged_df, output_path, title=chart_title, interval_seconds=interval_hours*3600) # 4. 任务总结 elapsed = time.time() - start_time logging.info(f"任务执行完毕,耗时 {elapsed:.2f} 秒。输出文件: {output_path}") logging.info("="*50) # --- 命令行接口 --- def parse_arguments(): """解析命令行参数。""" parser = argparse.ArgumentParser( description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter ) parser.add_argument( '--input-pattern', '-i', default='*.csv', help='输入数据文件的匹配模式(例如:sales_*.csv),默认为 *.csv' ) parser.add_argument( '--interval', '-t', type=int, default=24, help='数据采集间隔(小时),用于图表标题说明,默认为24' ) parser.add_argument( '--config', '-c', default='settings.yaml', help='配置文件路径(相对于config目录),默认为 settings.yaml' ) parser.add_argument( '--log-level', choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'], default='INFO', help='设置日志级别' ) return parser.parse_args() # --- 脚本入口 --- if __name__ == "__main__": args = parse_arguments() # 初始化目录 for dir_path in [DATA_INPUT_DIR, DATA_OUTPUT_DIR, CONFIG_DIR, LOG_DIR]: dir_path.mkdir(parents=True, exist_ok=True) # 设置日志 setup_logging(args.log_level) # 加载配置(命令行参数优先级高于配置文件) config = load_config(args.config) # 这里可以将配置与命令行参数合并使用 # 执行主流程 try: main_process(input_pattern=args.input_pattern, interval_hours=args.interval) except KeyboardInterrupt: logging.info("用户中断执行。") sys.exit(0) except Exception as e: logging.exception(f"任务执行过程中发生未预期错误: {e}") sys.exit(1)

配套的config/settings.yaml示例:

# 数据可视化配置 chart: style: "seaborn-v0_8" # matplotlib样式 width: 12 height: 6 dpi: 300 data: default_interval_hours: 24 required_columns: ["date", "value"] logging: # 更详细的配置可以在这里定义

requirements.txt示例:

pandas>=2.0.0 matplotlib>=3.7.0 PyYAML>=6.0

如何使用这个脚本:

  1. 开发环境运行:在项目根目录data_plotter/下,激活虚拟环境后,运行python src/plotter.py -i "sales_*.csv" -t 12
  2. 定时运行:将上述命令添加到cron或Windows任务计划中,实现“每隔一段时间画折线图”。
  3. 打包分发:使用PyInstaller打包,注意通过--add-dataconfig/data/目录(或至少是模板)包含进去。
    pyinstaller --onefile --add-data "config;config" --add-data "data;data" src/plotter.py

这个脚本展示了如何将一个简单的“运行py文件”的想法,系统化、工程化为一个健壮、可配置、易维护的工具。它处理了路径、日志、配置、错误、参数解析,并且为打包和部署做好了准备。这才是“运行”一个Python脚本的完整形态。

← 返回列表