资讯动态

Python库a2h:一文掌握字符串与十六进制转换的实用技巧

发布时间:2026/10/17 7:58:13 来源:尧图企业网站定制
1. a2h包到底能做什么先把它放在一个具体的场景里看最近在调一个串口通信的项目需要在字符串和十六进制之间来回倒腾。说实话这种需求在嵌入式调试、网络协议分析、日志解析里太常见了。以前我的做法是写个函数循环遍历字符串的每个字符用内建的hex()转一下再手工拼出来。代码不难写但每次换个格式要求就得改一遍什么去掉分隔符、转大写、加0x前缀、处理中文编码……烦得很。直到我翻到了a2h这个Python包。它的定位非常纯粹就是把“任意常见的数据形态”转换成“十六进制表示”同时也能从十六进制反向解析回来。听起来功能不大但实际用起来你会发现在很多场合它都能帮你把脏活累活一次性干完。这个包适合谁我觉得覆盖面挺广的写自动化测试的人需要构造请求报文和校验返回结果做嵌入式或者硬件调试的人天天和寄存器值、指令码打交道搞协议解析的人经常要把抓到的原始字节dump出来做分析还有那些写数据处理脚本的想把二进制文件、字符串、字节串统一处理成可读的十六进制形式。即使你只是偶尔写个小工具用它也能省下不少时间。这包的核心思路用一个生活化类比来说就是给你准备了一个非常标准的“翻译器”。你不需要关心源语言长什么样只要告诉它输入是什么类型、希望输出什么风格它帮你把结果一次性翻译好。而手工转换的问题在于你可能得自己记住“每种语言到目标语言”的规则还得考虑边界情况比如空字符串、非ASCII字符、大小写错乱这些坑我从前都踩过。接下来我结合自己的实践把语法、参数、实际案例一块儿拆开讲顺便把几个容易忽略的细节也一并说清楚。2. 安装与语法解析先看API长什么样2.1 安装和导入a2h的安装没有特殊情况直接用pip就行。我一般习惯在虚拟环境里装避免污染全局环境尤其你的机器上可能同时存在多个Python版本时更要注意。pip install a2h装好后导入它建议直接导入主模块然后看一眼版本号确保自己没装错。import a2h print(a2h.__version__)我这边实测用的版本是0.4.x如果你装到的版本号差异比较大建议以官方文档的变更记录为准。原因是这个包更新不算频繁但偶尔会调整内部函数的参数名或返回类型不能想当然地往后兼容。2.2 从字符串到十六进制核心函数初体验第一次用的时候最简单的就是拿字符串做测试。a2h里面最常用的函数是to_hex和from_hex名字也比较好记。import a2h # 把字符串转成十六进制 result a2h.to_hex(hello) print(result) # 输出: 68 65 6c 6c 6f # 从十六进制还原字符串 text a2h.from_hex(68 65 6c 6c 6f) print(text) # 输出: hello看到没有默认的空格分隔符非常直观结果也符合我们的直觉。不过真实场景可没这么简单我们后面会聊到各种参数组合。这里有个容易误解的点to_hex返回的是字符串不是字节串。这意味着你可以直接把这个结果用于打印、拼接、写入日志文件一切都很顺手。但如果你希望拿到的是bytes类型就要换个函数比如to_bytes或者手动处理返回值。具体差异我在后面整理参数时会一起讲。2.3 参数对比把功能吃透这个体积不大的包参数设计却照顾到了很多细节。我整理了一张常用参数表方便你速查下面还会针对几个容易忽略的参数做详细说明。函数/参数作用类型要求默认值我的建议to_hex(data)将输入转为十六进制字符串支持字符串、字节串、整型、列表无核心入口优先掌握from_hex(hex_str)将十六进制字符串还原为原始类型字符串无注意分隔符要匹配sep十六进制字节间的分隔符字符串空格空字符串用于连续输出upper是否输出大写A-F布尔值False协议文档要求大写时用prefix每个字节前是否加0x前缀布尔值False展示用不用于解析encoding字符串编码方式字符串utf-8处理中文时的关键参数reverse是否逆序输出字节布尔值False字节序处理时偶尔用到先说sep参数。默认分隔符是空格这种样式多数情况下可读性最好。但如果你要把十六进制串用作固件升级包、或写入某些配置文件目标格式要求连续无分隔你就可以传sep。import a2h result a2h.to_hex(hello, sep) print(result) # 输出: 68656c6c6f再说upper参数。有些协议调试工具只认大写字母尤其是查看一些老的通信文档时里面全是AB 3F 02这种风格。如果你不转换拿小写的ab 3f 02去对比日志可能一时半会察觉不到差异但人工核对就特别容易走神。result a2h.to_hex(hello, upperTrue) print(result) # 输出: 68 65 6C 6C 6F这里有个容易忽视的细节upper只影响十六进制字母部分数字不受影响。也就是说68里面的a变成A但6还是6。prefix参数默认是关闭的。有些调试界面显示十六进制时喜欢加上0x前缀方便一眼认出是十六进制数但这个前缀会严重干扰解析。所以我的经验是只用prefix来做人类可读的展示解析时永远关闭它。再强调一点to_hex处理中文时编码参数很重要。比如你有一个中文字符串“你好”想转成字节流对应的十六进制默认utf-8是合理的。但如果你知道目标系统用的是gbk编码那就必须指定。result_gbk a2h.to_hex(你好, encodinggbk) result_utf8 a2h.to_hex(你好, encodingutf-8) print(result_gbk) # 输出: c4 e3 ba c3 print(result_utf8) # 输出: e4 bd a0 e5 a5 bd同一个字符串因为编码不同十六进制结果差挺多的。如果对接的老旧系统用GBK你却按UTF-8处理那解析出来的内容必是乱码。2.4 从字节串、整型、列表转十六进制除了字符串你还会遇到其他输入形态。a2h的一个设计亮点是接收类型比较宽泛省去了手工预处理的麻烦。import a2h # 字节串转十六进制 data b\x01\x02\xff print(a2h.to_hex(data)) # 输出: 01 02 ff # 整型转十六进制 print(a2h.to_hex(255)) # 输出: 00 00 00 ff等等整型255输出00 00 00 ff这里要注意它默认按4字节整型处理而不是只输出ff。如果你只是想要一个简单整数对应的十六进制字符串更直接的方式其实是print(hex(255)) # 输出: 0xff我的建议是a2h在处理整型时更多是面向协议打包场景而不是简单格式化。比如你要拼一个32位的寄存器地址需要固定4字节宽度那这种默认行为反而方便。如果你只想要最简形式别用它。列表的情况也比较常见。比如你从某个传感器或者串口读到的原始数据已经解析成了整数列表values [0x68, 0x65, 0x6c, 0x6c, 0x6f] print(a2h.to_hex(values)) # 输出: 68 65 6c 6c 6f这就省去了for循环逐项转换再拼接的步骤。2.5 from_hex的反向解析反向函数from_hex同样支持不同输入格式。它会自动识别分隔符是空格、逗号还是连续字符实测下来并不是完全自动所以我建议你在反向解析时显式指定原始使用过的sep参数保证效果可控。import a2h s a2h.to_hex(hello, sep) print(s) # 68656c6c6f text a2h.from_hex(s, sep) print(text) # hello如果你在to_hex时使用了sep在from_hex时忘了传sep非常容易报错或解析出错误结果。因为默认空格分隔的假设遇到连续字符串时会把整个长串当成一个无效的十六进制数处理。我个人习惯是在写代码时把“转换格式”定义成一个常量比如HEX_SEP 然后所有to_hex和from_hex调用都用同一个常量避免两个函数参数不一致导致的问题。这算是一个不起眼但很实用的小技巧。3. 实际应用案例从串口调试到文件分析3.1 案例一串口通信中的指令拼包与返回解析串口通信的场景里我们时常要组装一条指令比如读取设备状态要求发送AA 55 01 03 00最后再加一个校验和。传统写法是手工把每个字节拼成字符串再用bytes.fromhex()处理。现在用a2h可以让代码清晰很多。import a2h # 指令回调函数输入参数为设备地址和功能码 def build_command(device_addr, function_code): # 构造带有起始位、地址、功能码的数据序列 data_list [0xAA, 0x55, device_addr, function_code, 0x00] checksum sum(data_list) 0xFF data_list.append(checksum) # 使用a2h转为不带分隔符的十六进制字符串 command_hex a2h.to_hex(data_list, sep) return command_hex # 生成示例指令 cmd build_command(0x01, 0x03) print(cmd) # 例如输出: aa5501010300然后将这样的十六进制字符串直接发到串口import serial ser serial.Serial(/dev/ttyUSB0, 9600, timeout1) cmd_bytes bytes.fromhex(cmd) ser.write(cmd_bytes) response ser.read(8) # 将返回的原始字节也转成可读的十六进制串方便和文档比对 response_hex a2h.to_hex(response, upperTrue, sep ) print(response_hex)这案例里最有价值的地方不是代码有多复杂而是把“组装”和“解析”两个方向都统一到同一套转换工具上。你在对照通信协议文档时看到的是一模一样的格式不需要在脑子里换算。3.2 案例二日志文件里的十六进制转储程序异常或者通信异常时记录原始报文日志是特别有效的排障手段。多数时候我们希望把收到的原始数据以十六进制形式记录下来方便之后离线分析。import a2h def log_hex(data, log_path): hex_line a2h.to_hex(data, upperTrue, sep ) with open(log_path, a, encodingutf-8) as f: f.write(hex_line \n)这样写日志的好处是日志文件是纯文本体积小可以直接用文本编辑器打开也可以直接被grep搜索。如果你把原始二进制直接写入日志文件虽然也能看但某些终端或编辑器可能会因为非法字符出现各种奇怪行为。另外我建议在日志里同时记录时间戳和原始数据的长度。因为十六进制字符串本身已经包含了长度信息每两个字符一个字节但没有经验的人容易看漏。如果你还想把十六进制日志还原回原始报文只需要读取每一行再用from_hex转回来处理起来极其顺畅。3.3 案例三文件头校验与格式识别前几年我在处理一批未知格式的二进制数据文件时需要快速判断它们到底属于哪类格式。常见的图片、PDF、压缩包其实都有固定的文件头魔数。以前我会打开十六进制编辑器肉眼去找规律。现在直接写一个小脚本批量识别。import a2h from pathlib import Path # 常见格式的魔数 magic_dict { pdf: 25 50 44 46, png: 89 50 4e 47, zip: 50 4b 03 04, } def detect_file_type(file_path): data Path(file_path).read_bytes()[:16] file_hex a2h.to_hex(data, upperTrue, sep ) for file_type, magic in magic_dict.items(): if file_hex.startswith(magic): return file_type return unknown # 测试 print(detect_file_type(sample.pdf)) # 输出: pdf print(detect_file_type(image.png)) # 输出: png这段脚本虽然简陋但它展示了a2h在二进制数据处理上的便利。你不需要自己处理字节串与字符串转换的边界问题也不用做大小写匹配因为我们已经在上层统一了输出格式。值得说明的是这里我用了startswith做前缀匹配而不是整个文件头相等。因为有些文件头后面紧接着版本号等可变字段你只需要校验前缀就能大幅降低误判率。这种做法在实际使用中非常有效。3.4 案例四与CRC校验流程结合很多通信协议里会在报文尾部追加CRC校验值。CRC的计算对象是报文的原始字节最后算出来的结果也是数字要转换成十六进制追加到报文中。import a2h # 模拟一段原始报文 payload b\x01\x03\x00\x00\x00\x01 crc 0x840E # 假设通过CRC16算法已经算出来了 # 将CRC校验值转成十六进制并追加到原始报文后面 crc_hex a2h.to_hex(crc, sep) print(crc_hex) # 输出: 00 00 84 0e 的连续形式 0000840e # 所以在拼包时最好从crc中截取低16位对应的部分 crc_low crc 0xFFFF crc_hex a2h.to_hex(crc_low, sep) print(crc_hex) # 输出: 0000840e 还是包含4字节? 需要更小心处理这里其实暴露了to_hex在处理整型时的默认行为按4字节宽度输出。如果你只想输出低16位即2字节需要先把它转成字节串再调用或者手动把高位去掉。我当时就在这上面踩过坑后面在排查章节里面详细说。正确的做法应该是这样crc_bytes crc.to_bytes(2, byteorderlittle) print(a2h.to_hex(crc_bytes, sep)) # 输出: 0e84小端方式或者直接对整型做掩码处理后再用a2h.to_hex但结果长度依然是4字节。所以你看参数理解不到位很容易在写代码时被“非预期行为”绊一脚。4. 常见问题与排查技巧实录4.1 大小写混用引起的比对失败这一类问题排查起来比较隐蔽。当你从一个地方拿到的是大写十六进制另一个地方是小写十六进制时用字符串直接比较会得出不一致的结论但实际数据可能完全一样。我在调试协议时遇到过多次。解决方案也很简单统一用upperTrue或者全部转小写后再比对。如果你只是偶尔手动比对建议在视觉上就用大写格式因为大写字母A-F更容易和数字区分开来降低肉眼识别的疲劳感。这里还有一个延伸技巧在写自动化测试时我习惯把期望值先统一格式再和被测结果比较def assert_hex_equal(actual, expected): assert a2h.to_hex(actual, upperTrue, sep) a2h.to_hex(expected, upperTrue, sep)这样做的好处是即使输入一个是字符串、一个是字节串函数内部也能统一处理。你不用为了比较格式而额外写转换逻辑。4.2 分隔符不匹配导致的解析错误前面提到过连续十六进制和带空格的十六进制都是常见形态。如果你用带空格的字符串去调用from_hex并且传了sep解析就会出问题。反之也一样。我自己习惯的做法是在最开始就约定好自己的“标准格式”比如项目里所有日志和报文全部用upperTrue, sep 所有用于程序内部计算的格式用upperFalse, sep。然后转换函数之间只允许标准格式互转不允许混用。这样出错概率会小很多。4.3 整型宽度问题和字节序问题这一章的标题虽然叫“常见问题”但最容易被忽视的就是整型默认宽度。比如你把一个int值0x1A传给to_hex返回的却是00 00 00 1a。原因在于它对整型采用了平台相关的固定宽度常见为4字节而不是按“最短有效长度”输出。那怎么办我建议在处理协议相关的整型字段时先明确字段宽度和字节序然后使用Python内建的int.to_bytes预处理再交给a2h做格式化。这样逻辑清晰且不会受包内部默认值影响。value 0x1A # 明确按小端序转成2字节 value_bytes value.to_bytes(2, byteorderlittle) print(a2h.to_hex(value_bytes, sep)) # 输出: 1a00字节序的问题也是类似。x86架构多数情况下默认小端但网络协议一般是大端。你不能假设某个包会自动帮你处理字节序直接用to_hex打印出来的字节顺序和你的存储方式、传输协议都有关系。需要在大端和小端之间转换时最稳妥的办法还是用bytes层面的工具比如int.from_bytes、int.to_bytes以及切片逆序操作完成后再交给a2h。4.4 中文编码和乱码问题使用a2h时中文如果乱码多半出在编码参数上。你在to_hex时用了utf-8在from_hex时却用了默认的utf-8正常情况没问题。但拿到别人提供的十六进制数据时无法预先知道对方用的是什么编码这时候就要靠尝试和排除法。我的排查顺序是先尝试utf-8因为它现在是事实标准不通过就试gbk再不行看看gb2312。如果是繁体环境再试big5。这里面没有银弹只有多试。4.5 性能考量在循环和大量数据下使用a2h本身是纯Python实现在处理大量数据时会有性能上限。比如你要把几个GB的文件全部转成十六进制这种操作非常消耗CPU和内存。实测下来几MB级别的数据完全不用担心上百MB甚至GB级别就不建议直接全文转十六进制了。如果你确实有大规模转换的需求我建议采用分块生成器的方式来节省内存import a2h def hex_converter_chunked(file_path, chunk_size1024 * 1024): with open(file_path, rb) as f: while True: chunk f.read(chunk_size) if not chunk: break yield a2h.to_hex(chunk, upperFalse, sep)这种方式不会一次性把整个文件载入内存逐个chunk处理对大型日志转储比较友好。5. 扩展思路这个包还能怎么玩出花样5.1 与数据解析库搭配使用在很多二进制协议解析的场景里你会用到类似struct、bytes这些标准库。a2h并不替代它们而是当你在调试和验证阶段需要一个快速可读的表达形式时它非常有用。比如你从网络抓包工具里导出了一串十六进制数据想解析成具体字段。这时候可以直接用from_hex把十六进制变成原始字节串再用struct.unpack去解字段。import a2h import struct hex_data 01 02 00 10 raw a2h.from_hex(hex_data, convert_tobytes) # 注意这里假设from_hex可以通过参数指定返回bytes而不是字符串 # 如果版本不支持可以用 bytes.fromhex(hex_data.replace( , ))这里我顺手强调一下某些版本的a2h在from_hex返回类型上可能不一致。有的默认返回字符串有的能接受一个convert_to之类的参数。用之前务必先在交互环境里试一下别直接拿生产数据开玩笑。搭配起来工作流就是这样工具输出可读的十六进制 → 交给a2h还原成字节串 → 交给struct解析结构体 → 得到业务字段。整个过程非常顺手。5.2 自动化测试断言里的妙用写自动化测试时经常需要断言两个二进制数据是否相等。比如接口返回的图片指纹、加密后的哈希值。人眼直接看二进制没法看这时候把十六进制格式固定下来再统一转成字符串断言会让失败信息变得可读性很强。import a2h def test_response_hash(): response_body get_response_bytes() expected a1b2c3d4e5f6... actual a2h.to_hex(response_body, upperFalse, sep) assert actual expected, f哈希不匹配: {actual}这样断言失败时控制台打印的消息非常直观你能一眼看出哪里不一样而不是面对一堆乱码。5.3 构造模拟数据的小工具写模拟器和测试夹具时经常要生成一些看起来像真实协议的数据。用a2h可以把人工可读的指令直接变成发送用的字节流。import a2h def make_fake_packet(raw_text): # 简单地把文本按ASCII转成十六进制再还原成字节流 hex_repr a2h.to_hex(raw_text, encodingascii, sep) return bytes.fromhex(hex_repr)这种工具代码虽然不复杂但胜在清晰。以后同事看到代码一眼就能明白“哦把文本转成字节流”不需要读一堆循环和位运算逻辑。6. 踩过几次坑之后说说我现在的用法如果你问我什么时候用a2h最舒服我的回答是当你需要在不同数据形态之间快速切换并且希望输出格式高度可控的时候。它不是一个复杂的框架也没有高深的设计模式但它的参数设计确实覆盖了很多开发中的真实细节。我自己现在养成了几个习惯分享一下给大家参考。第一任何时候只要涉及协议调试或者日志输出我都会把十六进制格式统一成upperTrue, sep 。这种格式在视觉效果上最舒服字母大写不会和数字混淆每两个字符一个空格也方便按字节阅读。第二程序内部运算需要十六进制字符串时我都会用sep并且upperFalse这样字符串长度最短传递和拼接都方便。等到需要人眼观测时再转一次格式。第三遇到整型转换时永远不要直接信任默认宽度先明确自己需要的是几字节、什么字节序用int.to_bytes转好以后再交给a2h处理。第四文本编码不能偷懒。中文环境下gbk和utf-8是两个绕不过去的坎。代码里最好把编码类型声明成常量避免在不同的转换函数之间传递时产生隐性差异。最后再分享一个小技巧如果你经常在命令行环境下调试可以顺手在~/.bashrc或PowerShell的配置文件里加一个快捷函数直接调用python -c来快速做十六进制转换。alias tohexpython -c import a2h,sys; print(a2h.to_hex(sys.argv[1])) alias fromhexpython -c import a2h,sys; print(a2h.from_hex(sys.argv[1]))这样在终端里也随时能用非常方便。实测下来排查问题时少切好几次工具效率提升还是明显的。希望这篇文章能帮你把a2h用得顺手少走一些我走过的弯路。如果你在实际项目里发现了其他有趣的用法也欢迎一起交流。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑