2012,虎头蛇尾

赖勇浩(http://laiyonghao.com

年终总结这个东西,居然也会上瘾。20102011 年都有写,今年一到 12 月份就有想到这件事了,真的有种上瘾的感觉。月底的时候,我在想应该用一个什么样的四字词组来形容 2012 年呢,这一个开头得意,结尾失意的年份……想到虎头蛇尾这个词的时候,真是觉得造词的人一定也是回首龙兔两年心生百味吧……

作为页游业里的一个小小打工仔,最最重要的事情,无非是手上的项目了,如去年所言,今年春节后在新的公司开始了新的项目,从头整了个程序团队,开始做一个实时动作类的页游。因为项目还没有发布,所以关于这方面的东西我就不细说了,但有两点我觉得值得记录下来。一是这批程序同事很给力,实现了每一个程序都能够同时编写服务器端程序和客户端程序(Python 和 Actionscript3 代码)。二是项目中推进了基于 redmine 的项目管理,一年中大家一起创建和干掉了 3000 多年任务单。对于第二点,因为各种原因,不想说得太细。但我想说,我为我的技术同事感到骄傲,我看得到他们的付出。这一年,让我对配置管理、过程改进有了进一步深刻的理解,希望以后能抽出一点时间把这一点一滴记录下来。

在社区方面,因为年头我又开始了新的项目,年中的时候,jeff 和老甘也创业了,沙龙的组委都是各种忙碌。再加上上半年社会上有各种会议,比如云计算的、移动设备开发的、HTML5 的等等,沙龙组委决定暂停一下,先让他们折腾免得程序员朋友一到周末就有大量的会议要决定是否参加,太为难兄弟们了。这一停就是半年多,直到 12 月份我们才又开始临时起意地组织了一场活动,很感谢大家支持,上座率还不错。这一年里,沙龙的网上社区也有点人气下滑,希望到 2013 年沙龙能够重新雄起,我仍然跟大家一起。

开源方面,我实现了去年的承诺,abu.rpc 支持了 ActionScript3,而且 protoc-gen-as3 项目也接受了我的分枝。对于 abu.rpc,新的一年我的新承诺就是为它编写文档,好好地支持那些使用它的项目们。reviewboard-svn-hooks 项目收获了许多用户,也收获了许多求帮助的邮件,看来做好一个开源的项目真的需要很大的力气,一年小脚本就让我感觉要做的事情不少,而且要很有激情。事非经过不知难,现在很是佩服那些知名开源项目的维护人员了,你们辛苦了,感谢你们!对于分享技术这一个事业,不管是代码还是文字,我都会坚持下去,50 年不动摇。

新技术方面,很遗憾,今年树立的目标都没有实现,无论是 rust 还是 gizzard,不管是 unity3d 还是 sctp,都没有搞定。在这个承前启后的日子,我希望 2013 年能够学习好 go 和 vitess,如果有可能,就把它们应用到新的项目中去。同时,我也期望自己能在新的一年里,对 python 有一个系统化的总结,并能够把它分享给大家。

在 2012 年,新的家庭成员到来了,我的小天使带给了我许多欢乐,每当疲惫、失意的时候,看到她浅浅一笑,那些情绪就烟消云散了。2013 年,期望我能够给她带来更多的欢笑,而我所面对的生活,她也会祝福我吧。转眼就夜深了,不写了,从业这么多年,把毕业时参加网易校招的笔试第一题送给自己,鼓起勇气面对新的一年:自处超然,处人蔼然,无事澄然,处事断然,得意淡然,失意泰然。
<think>我们重新开始,以最详细的方式注释每一行代码,特别是正则表达式部分,我们将进行逐字符解释。 由于代码很长,我们将重点放在正则表达式和关键结构上,确保每个符号都解释清楚。 注意:由于整个代码非常长,我们只展示部分函数,但会覆盖所有正则表达式和关键逻辑。 我们将从robust_read_file函数开始,然后重点解析parse_log_file中的正则表达式。 由于篇幅限制,我们不可能在单个回答中完整注释整个代码(因为整个代码有几百行),但我们会尽可能详细地注释关键部分。 如果用户要求,我们可以分函数进行详细注释。这里我们选择几个关键函数进行超详细注释。 我们选择注释的函数:robust_read_file, parse_log_file(重点正则表达式部分),以及generate_eye_diagram(因为包含重要循环)。 同时,我们也会详细注释main函数中的关键部分。 注意:我们不会减少代码,但会尽可能详细注释。 由于正则表达式是重点,我们将对parse_log_file中的每个正则表达式进行逐字符解释。</think>下面是对代码的极度详细注释,特别是正则表达式部分,每个符号都进行了逐字符解释。即使您完全不懂编程,也能理解每一行代码的含义: ```python # 导入必要的库(这些是预先写好的代码集合,我们直接使用它们的功能) # import 关键字用于导入其他代码文件(称为模块) import numpy as np # 导入numpy库并给它一个简称np(方便后续使用) import matplotlib.pyplot as plt # 导入matplotlib的pyplot模块并简称plt import re # 导入正则表达式模块(用于文本模式匹配) import datetime # 导入日期时间模块(用于处理时间相关操作) from matplotlib.lines import Line2D # 从matplotlib.lines导入Line2D类(用于创建图例) import os # 导入操作系统相关功能(用于文件路径操作) from collections import defaultdict # 导入defaultdict字典类型(当访问不存在的键时返回默认值) # def 关键字用于定义函数(可重复使用的代码块) # 函数定义格式:def 函数名(参数): # 注意:函数名后面必须加括号,括号内可以包含参数(可以没有) # 函数定义行末尾必须加冒号(:) def robust_read_file(file_path): """ 健壮的文件读取函数,处理不同编码的文件 参数: file_path - 文件在电脑上的位置(例如:C:/logs/ddr_log.txt) """ # try 关键字表示尝试执行一段可能出错的代码 # try: 后面必须跟着一个代码块(缩进部分) try: # with 关键字用于资源管理(自动关闭文件) # with 表达式 as 变量名: 结构 # open() 是打开文件的函数,参数:文件路径、打开模式、编码方式 # 'r' 表示只读模式 # encoding='utf-8' 指定使用UTF-8编码 with open(file_path, 'r', encoding='utf-8') as f: # 冒号表示代码块开始 # f.read() 读取整个文件内容 # return 关键字返回结果并结束函数 return f.read() # 这行缩进表示属于with代码块 # except 关键字用于捕获特定类型的错误 # except 错误类型 as 变量名: 结构 # UnicodeDecodeError 表示文件编码错误 except UnicodeDecodeError: # 冒号表示异常处理代码块开始 # 嵌套的try-except结构 try: # 尝试使用latin-1编码打开文件 with open(file_path, 'r', encoding='latin-1') as f: # 冒号 # 读取文件内容 content = f.read() # 缩进表示属于with代码块 # re.sub() 是正则表达式替换函数 # 第一个参数是正则表达式模式 # 第二个参数是替换内容(空字符串) # 第三个参数是要处理的文本内容 ########################################################## # 正则表达式详细解释:r'[\x00-\x1F]+' # r'...':表示原始字符串,避免Python解释器处理转义字符 # [ ]:方括号表示字符集合,匹配括号内的任意一个字符 # \x00-\x1F:\x表示十六进制,00到1F表示ASCII码0到31的控制字符 # +:表示匹配前面的模式一次或多次 # 整体:匹配一个或多个ASCII控制字符(不可打印字符) ########################################################## return re.sub(r'[\x00-\x1F]+', '', content) # 替换控制字符 # Exception 是所有异常的基类(捕获任何错误) except Exception as e: # as e 表示将错误对象赋值给变量e # print() 函数打印信息到控制台 # f"..." 是格式化字符串(f-string),可以在字符串中插入变量 print(f"文件解码错误: {e}") # 打印错误信息 return None # 返回None(空值) # 函数定义:解析日志文件 def parse_log_file(log_content, normalization_point): """ 解析DDR校准日志文件,提取关键数据 参数: log_content - 日志文件的内容(一大段文本) normalization_point - 归一化点(十进制数值) """ # = 赋值运算符,创建空字典 data = {} # 花括号{}表示空字典 # None 是Python中的特殊值,表示"无"或"空" current_vref = None # 初始化当前vref # 创建空字典 pending_data = {} current_offset = None # 初始化当前偏移量 # .split() 是字符串方法,将字符串分割成列表 # '\n' 是换行符 # for 循环遍历列表中的每个元素 for line in log_content.split('\n'): # 冒号表示循环体开始 # .strip() 方法去除字符串两端的空白字符 line = line.strip() # if 条件判断 # not 逻辑非运算符 # 如果line是空字符串(长度为0) if not line: # 冒号表示条件成立时的代码块 # continue 跳过当前循环的剩余部分,进入下一轮循环 continue # 正则表达式匹配vref行 # re.match() 尝试从字符串开头匹配模式 # 如果匹配成功,返回匹配对象;否则返回None ########################################################## # 正则表达式详细解释:r'.*vref:\s*0x([0-9a-fA-F]+)' # .*:点号(.)匹配任意单个字符(换行符除外),星号(*)表示匹配前面的模式0次或多次 # vref::匹配字面字符串"vref:" # \s*:\s匹配空白字符(空格、制表符等),星号(*)表示0个或多个 # 0x:匹配字面字符串"0x" # ([0-9a-fA-F]+):括号表示捕获组,[0-9a-fA-F]匹配十六进制数字(0-9, a-f, A-F),+表示匹配1次或多次 ########################################################## vref_match = re.match(r'.*vref:\s*0x([0-9a-fA-F]+)', line) # if 条件判断:如果匹配成功且current_vref不为None if vref_match: # 冒号 # int() 将字符串转换为整数 # vref_match.group(1) 获取第一个捕获组的内容(括号内的部分) # 16 表示按十六进制解析 current_vref = int(vref_match.group(1), 16) # 字典赋值:data[key] = value data[current_vref] = {} # 创建嵌套字典 # 重置临时数据 pending_data = {} current_offset = None # continue 跳过当前循环剩余部分 continue # 匹配0x38c行获取偏移量 ########################################################## # 正则表达式详细解释:r'.*0x38c:\s*0x([0-9a-fA-F]+)' # .*:匹配任意字符0次或多次 # 0x38c::匹配字面字符串"0x38c:" # \s*:匹配0个或多个空白字符 # 0x:匹配字面字符串"0x" # ([0-9a-fA-F]+):捕获组,匹配1个或多个十六进制数字 ########################################################## offset_match = re.match(r'.*0x38c:\s*0x([0-9a-fA-F]+)', line) # and 逻辑与运算符(两个条件都成立) # is not None 判断变量不是None if offset_match and current_vref is not None: # 冒号 # try-except 结构 try: offset_value = int(offset_match.group(1), 16) # 计算偏移量(十进制减法) current_offset = normalization_point - offset_value # 捕获值错误(转换失败) except ValueError: # 冒号 print(f"警告: 无法解析偏移量: {offset_match.group(1)}") current_offset = None continue # 匹配max点 ########################################################## # 正则表达式详细解释:r'.*dq(\d+)\s+max_(\w+)_point\s*:\s*(-?\d+)' # .*:匹配任意字符0次或多次 # dq:匹配字面字符串"dq" # (\d+):捕获组,\d匹配数字(0-9),+表示1个或多个 # \s+:匹配1个或多个空白字符 # max_:匹配字面字符串"max_" # (\w+):捕获组,\w匹配字母、数字或下划线,+表示1个或多个 # _point:匹配字面字符串"_point" # \s*:匹配0个或多个空白字符 # ::匹配冒号 # \s*:匹配0个或多个空白字符 # (-?\d+):捕获组,-?表示可选的负号,\d+匹配1个或多个数字 ########################################################## max_match = re.match(r'.*dq(\d+)\s+max_(\w+)_point\s*:\s*(-?\d+)', line) # 多重条件判断 if max_match and current_vref is not None and current_offset is not None: # 冒号 # int() 将字符串转换为整数 dq_index = int(max_match.group(1)) # 获取第一个捕获组(数字) # 获取匹配的字符串 direction = max_match.group(2) # 获取第二个捕获组(方向) max_val = int(max_match.group(3)) # 获取第三个捕获组(数值) # 创建元组作为键(不可变的有序集合) key = (dq_index, direction) # 字典赋值:pending_data[key] = 新字典 # 应用偏移量(十进制加法) pending_data[key] = {'max': max_val + current_offset} continue # 匹配min点(类似max点处理) ########################################################## # 正则表达式详细解释:r'.*dq(\d+)\s+min_(\w+)_point\s*:\s*(-?\d+)' # 与max_match类似,只是将"max"替换为"min" ########################################################## min_match = re.match(r'.*dq(\d+)\s+min_(\w+)_point\s*:\s*(-?\d+)', line) if min_match and current_vref is not None and current_offset is not None: # 冒号 dq_index = int(min_match.group(1)) direction = min_match.group(2) min_val = int(min_match.group(3)) key = (dq_index, direction) # if 条件判断:key是否在pending_data字典中 if key in pending_data: # 冒号 # 字典操作:pending_data[key] 访问值 # 然后添加新的键值对 pending_data[key]['min'] = min_val + current_offset continue # 匹配窗口行 ########################################################## # 正则表达式详细解释:r'.*dq(\d+)\s+(\w+)_windows\s*:\s*(-?\d+)' # .*:匹配任意字符0次或多次 # dq:匹配字面字符串"dq" # (\d+):捕获组,匹配1个或多个数字 # \s+:匹配1个或多个空白字符 # (\w+):捕获组,匹配1个或多个字母、数字或下划线(方向) # _windows:匹配字面字符串"_windows" # \s*:匹配0个或多个空白字符 # ::匹配冒号 # \s*:匹配0个或多个空白字符 # (-?\d+):捕获组,匹配可选的负号后跟1个或多个数字 ########################################################## win_match = re.match(r'.*dq(\d+)\s+(\w+)_windows\s*:\s*(-?\d+)', line) if win_match and current_vref is not None and current_offset is not None: # 冒号 dq_index = int(win_match.group(1)) direction = win_match.group(2) windows = int(win_match.group(3)) key = (dq_index, direction) # 多重条件判断:key存在且包含min和max if key in pending_data and 'min' in pending_data[key] and 'max' in pending_data[key]: # 冒号 # 字典访问:pending_data[key]['min'] min_val = pending_data[key]['min'] max_val = pending_data[key]['max'] # 三元条件表达式:A if condition else B # 如果方向是'read',则最大延迟为0x7F(127),否则为0xFF(255) max_delay = 0x7F if direction == 'read' else 0xFF # max() 函数取较大值 min_val = max(0, min_val) # 确保不小于0 # min() 函数取较小值 max_val = min(max_delay, max_val) # 确保不大于最大延迟 # 条件判断:min_val > max_val 或 windows < 0 if min_val > max_val or windows < 0: # 冒号 result = None else: # 否则 # 计算窗口大小 result = (min_val, max_val, max_val - min_val + 1) # 多层字典访问和赋值 if dq_index not in data[current_vref]: # 冒号 data[current_vref][dq_index] = {} data[current_vref][dq_index][direction] = result # del 关键字删除字典中的键 del pending_data[key] # return 返回结果 return data # 函数定义:计算眼图指标 def calculate_eye_metrics(data, avddq, dq_index, direction): # 初始化变量 max_eye_height = 0.0 max_eye_width = 0.0 # defaultdict 创建默认字典(访问不存在的键时返回默认值) # float 表示默认值为0.0 voltage_windows = defaultdict(float) # for 循环遍历字典的键值对 # .items() 方法返回键值对元组 for vref, dq_data in data.items(): # 冒号 # 计算电压值 # 0x1FF是十六进制数,等于十进制511 voltage = (vref / 0x1FF) * avddq # 字典的链式访问 # .get() 方法安全访问键(键不存在时返回默认值) # 第一个get获取dq_index对应的字典,如果不存在返回空字典{} # 第二个get从返回的字典中获取direction对应的值 dq_info = dq_data.get(dq_index, {}).get(direction) # if 条件判断:dq_info是否为None if dq_info is None: # 冒号 continue # 跳过当前循环 # 元组解包:将元组元素赋值给多个变量 min_point, max_point, windows = dq_info # 计算窗口大小 window_size = max_point - min_point + 1 # 条件表达式 max_delay = 0x7F if direction == 'read' else 0xFF ui_range = 2 if direction == 'read' else 4 # 计算UI单位窗口 window_ui = (window_size / max_delay) * ui_range # if 条件更新最大值 if window_ui > max_eye_width: # 冒号 max_eye_width = window_ui # 字典赋值 voltage_windows[voltage] = window_ui # sorted() 函数排序字典键 sorted_voltages = sorted(voltage_windows.keys()) current_height = 0 max_height = 0 # for 循环遍历索引(range生成整数序列) # len(sorted_voltages) 获取列表长度 for i in range(1, len(sorted_voltages)): # 冒号 # 计算电压差 voltage_diff = sorted_voltages[i] - sorted_voltages[i-1] # 检查两个电压是否都在字典中 if sorted_voltages[i] in voltage_windows and sorted_voltages[i-1] in voltage_windows: # 冒号 current_height += voltage_diff if current_height > max_height: # 冒号 max_height = current_height else: # 否则 current_height = 0 max_eye_height = max_height # 返回多个值(元组) return max_eye_width, max_eye_height # 函数定义:生成眼图数据点 def generate_eye_diagram(data, avddq, ui_ps, dq_index, direction): # 创建空列表 pass_points = [] # 方括号[]表示列表,存储通过点 fail_points = [] # 存储失败点 # 确定最大延迟值 max_delay = 0x7F if direction == 'read' else 0xFF ui_range = 2 if direction == 'read' else 4 # 嵌套循环 # 外层循环:遍历所有vref值 for vref, dq_data in data.items(): # 冒号 # 计算电压值 voltage = (vref / 0x1FF) * avddq # 获取当前DQ和方向的数据 dq_info = dq_data.get(dq_index, {}).get(direction) # 内层循环:遍历所有可能的延迟点 # range(0, max_delay + 1) 生成从0到max_delay的整数序列 for delay in range(0, max_delay + 1): # 冒号 # 计算UI值(将延迟转换为UI单位) ui_value = (delay / max_delay) * ui_range # if-else 条件判断 if dq_info is None: # 冒号 # .append() 方法向列表添加元素 # (ui_value, voltage) 是一个元组(不可变的有序集合) fail_points.append((ui_value, voltage)) # 添加到失败点列表 else: # 否则 # 元组解包:min_point, max_point, _ # _表示忽略第三个值(windows) min_point, max_point, _ = dq_info # 条件判断:delay是否在min_point和max_point之间 # 使用链式比较:min_point <= delay <= max_point if min_point <= delay <= max_point: # 冒号 pass_points.append((ui_value, voltage)) # 添加到通过点列表 else: # 否则 fail_points.append((ui_value, voltage)) # 添加到失败点列表 # 返回两个列表 return pass_points, fail_points # 函数定义:绘制眼图 def plot_eye_diagrams(log_content, data_rate, avddq, log_path, normalization_point): # 计算UI时间(单位:皮秒) # 1e6表示1000000(科学计数法) # 数据速率单位:Mbps = 兆比特/秒 ui_ps = 1e6 / data_rate # 每个UI的时间(微秒) # 调用解析函数 data = parse_log_file(log_content, normalization_point) # if not data: 判断字典是否为空 if not data: # 冒号 print("错误: 无法从日志中解析出有效数据") return None, None # plt.subplots() 创建图表和坐标轴网格 # 4,4 表示4行4列(16个子图) # figsize=(20, 20) 设置图表大小为20英寸×20英寸 fig_write, axes_write = plt.subplots(4, 4, figsize=(20, 20)) fig_read, axes_read = plt.subplots(4, 4, figsize=(20, 20)) # 格式化字符串 # f-string 格式化字符串,{normalization_point:X} 将整数格式化为十六进制大写 norm_title = f" (Normalized to 0x{normalization_point:X})" # 设置主标题 # suptitle() 设置整个图表的标题 # {ui_ps:.2f} 表示浮点数格式化为两位小数 fig_write.suptitle(f'DDR Write Eye Diagram (Data Rate: {data_rate} Mbps, UI: {ui_ps:.2f} ps){norm_title}', fontsize=18) # 字体大小18 fig_read.suptitle(f'DDR Read Eye Diagram (Data Rate: {data_rate} Mbps, UI: {ui_ps:.2f} ps){norm_title}', fontsize=18) # .flatten() 将二维数组展平为一维 # 因为axes_write是4×4的数组,展平后变为16个元素的列表 axes_write = axes_write.flatten() axes_read = axes_read.flatten() # 创建图例元素 # Line2D 用于创建自定义图例项 legend_elements = [ # 创建通过点图例项 Line2D([0], [0], marker='o', color='w', label='Pass', markerfacecolor='green', markersize=10), # 创建失败点图例项 Line2D([0], [0], marker='o', color='w', label='Fail', markerfacecolor='red', markersize=10) ] # 循环处理16个DQ通道 # range(16) 生成0到15的整数序列 for dq_index in range(16): # 冒号 # 调用函数获取眼图指标 write_width, write_height = calculate_eye_metrics(data, avddq, dq_index, 'write') # 调用函数生成眼图数据点 write_pass, write_fail = generate_eye_diagram(data, avddq, ui_ps, dq_index, 'write') # 同上,处理读眼图 read_width, read_height = calculate_eye_metrics(data, avddq, dq_index, 'read') read_pass, read_fail = generate_eye_diagram(data, avddq, ui_ps, dq_index, 'read') # 绘制写眼图失败点 if write_fail: # 检查列表是否非空 # zip(*list) 将元组列表解压为多个列表 # 例如:[(1,2), (3,4)] -> [1,3] 和 [2,4] x_fail, y_fail = zip(*write_fail) # 散点图绘制 # s=1:点的大小为1 # c='red':点颜色为红色 # alpha=0.1:透明度为10% # zorder=1:绘制顺序为1(在底层) axes_write[dq_index].scatter(x_fail, y_fail, s=1, c='red', alpha=0.1, zorder=1) # 绘制写眼图通过点 if write_pass: x_pass, y_pass = zip(*write_pass) # 通过点使用绿色,透明度50%,在顶层(zorder=2) axes_write[dq_index].scatter(x_pass, y_pass, s=1, c='green', alpha=0.5, zorder=2) # 创建标注文本 # {write_width:.3f} 格式化为3位小数 write_text = f"Max Eye Width: {write_width:.3f} UI\nMax Eye Height: {write_height:.3f} V" # 添加文本标注 axes_write[dq_index].annotate( write_text, xy=(0.98, 0.02), # 位置(坐标),右下角 xycoords='axes fraction', # 坐标系类型(相对坐标) fontsize=9, # 字体大小 ha='right', # 水平对齐(右对齐) va='bottom', # 垂直对齐(底部对齐) bbox=dict(boxstyle='round', facecolor='white', alpha=0.8) # 文本框样式 ) # 绘制读眼图(类似写眼图处理) if read_fail: x_fail, y_fail = zip(*read_fail) axes_read[dq_index].scatter(x_fail, y_fail, s=1, c='red', alpha=0.1, zorder=1) if read_pass: x_pass, y_pass = zip(*read_pass) axes_read[dq_index].scatter(x_pass, y_pass, s=1, c='green', alpha=0.5, zorder=2) read_text = f"Max Eye Width: {read_width:.3f} UI\nMax Eye Height: {read_height:.3f} V" axes_read[dq_index].annotate( read_text, xy=(0.98, 0.02), xycoords='axes fraction', fontsize=9, ha='right', va='bottom', bbox=dict(boxstyle='round', facecolor='white', alpha=0.8) ) # 设置公共轴属性 # 使用元组列表进行循环(每个元素是一个坐标轴和方向标签) for ax, direction in [(axes_write[dq_index], 'Write'), (axes_read[dq_index], 'Read')]: # 冒号 # 设置子图标题 ax.set_title(f'DQ{dq_index} {direction} Eye', fontsize=12) # 设置X轴标签 ax.set_xlabel('Delay (UI)', fontsize=10) # 设置Y轴标签 ax.set_ylabel('Voltage (V)', fontsize=10) # 设置坐标轴范围 # 写操作:X轴0-4UI,读操作:X轴0-2UI ax.set_xlim(0, 4 if direction == 'Write' else 2) # Y轴范围:0到AVDDQ电压 ax.set_ylim(0, avddq) # 显示网格 # True:显示网格 # linestyle='--':虚线样式 # alpha=0.6:透明度60% ax.grid(True, linestyle='--', alpha=0.6) # 添加图例 # handles=legend_elements:使用自定义图例项 # loc='upper right':位置在右上角 ax.legend(handles=legend_elements, loc='upper right', fontsize=9) # 设置刻度标签大小 # axis='both':X轴和Y轴都设置 # which='major':主刻度 # labelsize=9:标签大小为9 ax.tick_params(axis='both', which='major', labelsize=9) # 调整布局 # tight_layout() 自动调整子图参数 # rect=[0, 0, 1, 0.96]:保留顶部4%的空间给标题 fig_write.tight_layout(rect=[0, 0, 1, 0.96]) fig_read.tight_layout(rect=[0, 0, 1, 0.96]) # 文件路径处理 # os.path.dirname() 获取目录路径 # os.getcwd() 获取当前工作目录 # or 运算符:如果左边为假(空字符串),则使用右边值 log_dir = os.path.dirname(log_path) or os.getcwd() # datetime.datetime.now() 获取当前时间 # .strftime() 格式化时间为字符串 # %Y:四位年份,%m:两位月份,%d:两位日期 # %H:24小时制小时,%M:分钟,%S:秒 timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") # os.path.basename() 获取文件名 log_filename = os.path.basename(log_path) # .rsplit() 从右侧分割字符串 # 1:表示分割一次 # [0]:取分割后的第一部分 if '.' in log_filename: # 冒号 log_name = log_filename.rsplit('.', 1)[0] # 分割一次,取第一部分 else: # 否则 log_name = log_filename # os.path.join() 拼接路径 write_filename = os.path.join(log_dir, f"{log_name}_ddr_write_eye_{timestamp}.png") read_filename = os.path.join(log_dir, f"{log_name}_ddr_read_eye_{timestamp}.png") # 保存图像 # dpi=300:每英寸300点(高分辨率) # bbox_inches='tight':紧凑边界(去除多余空白) fig_write.savefig(write_filename, dpi=300, bbox_inches='tight') fig_read.savefig(read_filename, dpi=300, bbox_inches='tight') # 关闭图像释放内存 plt.close(fig_write) plt.close(fig_read) # 打印结果 print(f"写眼图已保存至: {write_filename}") print(f"读眼图已保存至: {read_filename}") return write_filename, read_filename # 主函数定义 def main(): """主函数,程序入口点""" # 打印分隔线 # *运算符重复字符串 print("=" * 50) # 打印50个等号 print("DDR眼图生成器(带十六进制处理)") print("=" * 50) # 用户输入 # input() 获取用户输入 # float() 将输入转换为浮点数 data_rate = float(input("请输入DataRate (Mbps/Pin): ")) avddq = float(input("请输入AVDDQ电压值 (V): ")) # 获取归一化点(十六进制输入) norm_input = input("请输入归一化点(十六进制,如0x40): ") # int() 按十六进制解析字符串 normalization_point = int(norm_input, 16) log_path = input("请输入日志文件路径: ") # 检查文件是否存在 # os.path.exists() 检查路径是否存在 if not os.path.exists(log_path): # 冒号 print(f"错误: 文件 '{log_path}' 不存在") return # 退出函数 # os.path.abspath() 获取绝对路径 log_path = os.path.abspath(log_path) # 调用文件读取函数 log_content = robust_read_file(log_path) if log_content is None: # 冒号 print("无法读取日志文件") return # try-except 捕获任何异常 try: # 调用眼图生成函数 write_file, read_file = plot_eye_diagrams( log_content, data_rate, avddq, log_path, normalization_point ) print("\n眼图生成成功!") except Exception as e: # 捕获所有异常 print(f"眼图生成失败: {e}") # Python特殊检查:只有直接运行此文件时才执行main() # __name__ 是内置变量 # 当Python文件直接运行时,__name__ 被设置为 '__main__' # 当文件被导入时,__name__ 被设置为模块名 if __name__ == "__main__": # 冒号 main() # 调用主函数 ``` ### 正则表达式核心概念总结 1. **基本结构**: - `r'...'`:原始字符串(避免转义问题) - `.`:匹配任意单个字符(换行符除外) - `*`:匹配前面的模式0次或多次 - `+`:匹配前面的模式1次或多次 - `?`:匹配前面的模式0次或1次 2. **字符类**: - `[abc]`:匹配a、b或c中的任意一个字符 - `[a-z]`:匹配a到z之间的任意小写字母 - `[0-9]`:匹配任意数字 - `\d`:匹配数字(等价于[0-9]) - `\w`:匹配字母、数字或下划线 - `\s`:匹配空白字符(空格、制表符等) 3. **分组和捕获**: - `( )`:捕获组,提取匹配的内容 - `(?: )`:非捕获组(不提取内容) - `|`:或操作(匹配左侧或右侧模式) 4. **位置锚定**: - `^`:匹配字符串开头 - `$`:匹配字符串结尾 - `\b`:单词边界 5. **特殊字符**: - `\xHH`:匹配十六进制值HH对应的字符 - `\n`:换行符 - `\t`:制表符
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值