Python 标准库 csv 模块权威指南:CSV 读写、方言(Dialect)机制与格式嗅探
本文以 CPython 仓库中 Doc/library/csv.rst 为骨架,结合其纯 Python 封装层 Lib/csv.py 与 C 加速核心 Modules/_csv.c 的源码,系统讲解 csv 模块的读写 API、方言与格式化参数、六个引用常量、Sniffer 自动探测机制以及错误处理实践。读完本文,你将能熟练读写 Excel/UNIX 风格表格数据、注册自定义方言、处理带引号与转义字符的复杂字段,并利用 Sniffer 猜测未知文件的格式。
CSV 格式的由来与 csv 模块的定位
CSV(Comma Separated Values,逗号分隔值)是电子表格与数据库之间最常用的导入导出格式。虽然社区曾尝试用 RFC 4180 对其标准化,但在标准出现之前的多年使用中,不同应用产出的数据在分隔符、引号字符与换行约定上存在大量细微差异,导致"跨来源处理 CSV"成为一件麻烦事。幸运的是,这些差异只集中在分隔符与引用字符层面,整体结构足够相似,因而可以用一个统一模块在幕后隐藏读写细节,这就是 csv 模块的出发点。
csv 模块实现了 PEP 305(CSV File API)所描述的接口,提供以下能力:
- 让程序员以"Excel 偏好的格式"写入数据、读取 Excel 生成的文件,而无需了解 Excel 的具体 CSV 规则;
- 描述其他应用所理解的 CSV 格式,或自定义专用 CSV 格式;
reader与writer对象负责处理序列(sequence)形式的行;DictReader与DictWriter类负责以字典形式读写数据。
在代码结构上,CPython 将 csv 拆成两层:Lib/csv.py 从 _csv 导入 Error、reader、writer、方言注册等底层构件并实现 Dialect/excel/excel_tab/unix_dialect/DictReader/DictWriter/Sniffer 等类;真正的解析状态机与写出行拼接逻辑位于 Modules/_csv.c。源码注释明确提示"不应直接使用 _csv,请导入 csv.py 模块"。
模块核心函数:从 reader 到方言管理
本节对应原文档 "Module Contents" 一节的函数部分,逐一说明签名、语义与底层实现。
reader(csvfile, /, dialect='excel', **fmtparams)
返回一个处理 csvfile 各行数据的 reader 对象。csvfile 必须是"字符串的可迭代对象",最常见的是以 newline='' 打开的文件对象或 list;若传入文件对象而未指定 newline='',带引号字段内嵌的换行将无法被正确解析,且在 \r\n 行尾平台上写入时还会多出一个 \r——这是因为 csv 模块会自己做(universal)换行处理,详见原文档脚注 [1]。
- dialect:可选,可以是
Dialect子类的实例,或list_dialects()返回的名字字符串; - fmtparams:可选关键字参数,用于覆盖当前方言中的单个格式化参数(完整清单见后文"格式化参数"一节)。
每次读取到的行以字符串列表返回,默认不做任何类型转换;仅当指定 QUOTE_NONNUMERIC 时,未加引号的字段才会被转换为 float。
官方示例(文件 eggs.csv 的内容为 Spam Spam Spam Spam Spam |Baked Beans| 与 Spam |Lovely Spam| |Wonderful Spam|):
import csv
with open('eggs.csv', newline='') as csvfile:
spamreader = csv.reader(csvfile, delimiter=' ', quotechar='|')
for row in spamreader:
print(', '.join(row))
writer(csvfile, /, dialect='excel', **fmtparams)
返回一个 writer 对象,负责把用户数据转换为定界字符串并写入 csvfile。csvfile 可以是任何带 write 方法的对象,若为文件对象同样应以 newline='' 打开。
两个关键的数据转换规则(对应源码中 csv_writerow_lock_held 的主循环):
None被写为空字符串——这是为了对接 DB-API:SQL 的NULL值可以直接经cursor.fetch*转储到 CSV,无需预处理。不过原文也明确指出这是不可逆的转换;- 所有非字符串数据先经
str()字符串化再写出。在 Modules/_csv.c 中,非字符串字段正是通过PyObject_Str(field)转换后统一进入缓冲区拼接的。
官方写入示例:
import csv
with open('eggs.csv', 'w', newline='') as csvfile:
spamwriter = csv.writer(csvfile, delimiter=' ',
quotechar='|', quoting=csv.QUOTE_MINIMAL)
spamwriter.writerow(['Spam'] * 5 + ['Baked Beans'])
spamwriter.writerow(['Spam', 'Lovely Spam', 'Wonderful Spam'])
方言注册表管理:register_dialect / unregister_dialect / get_dialect / list_dialects
这四个函数构成全局方言注册表(在 C 层由 _csvstate->dialects 字典承载):
register_dialect(name, /, dialect='excel', **fmtparams):把 dialect 与字符串 name 关联。可以传入Dialect子类、fmtparams关键字参数、或两者兼有(关键字参数优先覆盖方言中的参数);unregister_dialect(name):从注册表删除名为 name 的方言;若 name 未注册则抛出csv.Error;get_dialect(name):返回与 name 关联的方言(返回不可变的Dialect),未注册时同样抛出Error;list_dialects():返回所有已注册方言的名字。
模块导入时即在注册表中登记了三个内置方言(见 Lib/csv.py 顶层代码):excel、excel-tab 与 unix。
field_size_limit() / field_size_limit(new_limit)
返回解析器允许的最大字段长度;传入 new_limit 时则设置新上限。该上限的实现位于 Modules/_csv.c 的 parse_add_char:当累计字符数 field_len 达到 field_limit 时抛出 "field larger than field limit (%zd)" 错误。字段缓冲区以 4096 字符起步、容量不足时按 2 倍倍增(parse_grow_buff),但单字段总长度受此上限约束。默认值足够处理常规表格,但遇到超长文本字段(如整列嵌入的 JSON)时需按需调大。Lib/test/test_csv.py 的 TestFieldSizeLimit 用例验证了设置、恢复与非法参数的报错行为。
字典化读写:DictReader 与 DictWriter
原文档将两个字典映射类与 Dialect、Sniffer 一并列为模块类,这里重点展开它们的参数语义。
DictReader(f, fieldnames=None, restkey=None, restval=None, dialect='excel', *args, **kwds)
行为类似普通 reader,但把每行信息映射为以 fieldnames 为键的 dict:
- fieldnames 是序列;省略时取文件首行值作为字段名,且首行会从结果中剔除;显式提供时,首行数据会正常出现在结果里。无论字段名如何确定,字典都保持原始顺序。若传入的是迭代器,会被强制转成
list; - 行字段多于 fieldnames:多余数据放入列表,挂在 restkey 指定的键下(默认
None); - 非空行字段少于 fieldnames:缺失值以 restval 填充(默认
None); - 其余可选/关键字参数全部透传给底层
reader实例。
实现上(Lib/csv.py DictReader.__next__),字段名通过惰性属性 fieldnames 在首次访问或读取首条记录时初始化;空白行会被跳过(避免产出一堆 None 的字典);当 lf < lr 时把余下列挂在 restkey 下,lf > lr 时用 restval 补齐。返回行类型在 3.6 为 OrderedDict、3.8 起改为普通 dict。
官方示例:
>>> import csv
>>> with open('names.csv', newline='') as csvfile:
... reader = csv.DictReader(csvfile)
... for row in reader:
... print(row['first_name'], row['last_name'])
...
Eric Idle
John Cleese
>>> print(row)
{'first_name': 'John', 'last_name': 'Cleese'}
其中 names.csv 内容为:
first_name,last_name
Eric,Idle
John,Cleese
DictWriter(f, fieldnames, restval='', extrasaction='raise', dialect='excel', *args, **kwds)
行为类似普通 writer,但把字典映射为输出行:
- fieldnames 必须是键序列,它决定各字典值写入文件的顺序。注意:与
DictReader不同,DictWriter的fieldnames不可省略; - restval:当传入字典缺少 fieldnames 中的某个键时,写入该默认值(默认空字符串
''); - extrasaction:当传入字典含有 fieldnames 之外的键时的行为——默认
'raise'抛出ValueError;设为'ignore'则忽略多余键。构造函数的_dict_to_list即先检查rowdict.keys() - self.fieldnames是否非空,再按fieldnames顺序用rowdict.get(key, self.restval)生成输出序列; - 其余参数透传给底层
writer。
官方示例:
import csv
with open('names.csv', 'w', newline='') as csvfile:
fieldnames = ['first_name', 'last_name']
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
writer.writerow({'first_name': 'Baked', 'last_name': 'Beans'})
writer.writerow({'first_name': 'Lovely', 'last_name': 'Spam'})
writer.writerow({'first_name': 'Wonderful', 'last_name': 'Spam'})
生成 names.csv 内容:
first_name,last_name
Baked,Beans
Lovely,Spam
Wonderful,Spam
内置方言与 Dialect 容器类
Dialect 是一个容器类,其属性描述解析/写入时如何处理双引号、空白、分隔符等。由于缺少严格规范,不同应用产出细节各异的 CSV,Dialect 实例正是用来约束 reader/writer 行为的。所有可用方言名由 list_dialects() 返回,可在 reader/writer 构造时通过 dialect 参数引用,例如:
import csv
with open('students.csv', 'w', newline='') as csvfile:
writer = csv.writer(csvfile, dialect='unix')
CPython 内置三个方言类(定义与注册均在 Lib/csv.py 顶部,底层属性表见 Modules/_csv.c 的 DialectObj):
| 方言类 | 注册名 | 关键属性 | 适用场景 |
|---|---|---|---|
excel() |
'excel' |
delimiter=','、quotechar='"'、doublequote=True、skipinitialspace=False、lineterminator='\r\n'、quoting=QUOTE_MINIMAL |
Excel 生成的 CSV |
excel_tab() |
'excel-tab' |
继承 excel,仅 delimiter='\t' |
Excel 生成的 TAB 定界文件 |
unix_dialect() |
'unix'(3.2 新增) |
delimiter=','、quotechar='"'、doublequote=True、skipinitialspace=False、lineterminator='\n'、quoting=QUOTE_ALL |
UNIX 系统生成的 CSV(所有字段都加引号) |
从源码结构看,excel_tab 直接继承自 excel 只改写分隔符;unix_dialect 与 excel 的关键差异在于行终止符为 '\n' 且采用 QUOTE_ALL。另外 Lib/csv.py 的 Dialect 基类还实现了 __replace__(copy.replace 协议)与 _validate:前者用于在副本上替换指定格式化参数并重新校验,未知关键字会抛 TypeError;后者把 self 交给 C 层 _Dialect 构造以校验属性取值是否合法。
Sniffer:自动识别 CSV 格式
Sniffer 类用于反推(sniff)一个未知 CSV 文件的格式,提供两个方法与一个属性。
sniff(sample, delimiters=None)
分析给定的 sample 文本,返回反映其格式参数(delimiter、quotechar、escapechar、doublequote、skipinitialspace、lineterminator)的 Dialect 子类。可选参数 delimiters 为字符串,限定候选分隔符集合。
原文档强调其判定策略:用所有合理的参数组合去试解析样本,选择把样本切分为"字段数最一致的行"的组合,因此返回的方言与 reader 实际解析结果一致。若没有任何组合匹配(尤其是单列无分隔符的情形),抛出 csv.Error。若多个组合表现同样好(例如 , 与 ; 都能一致地切分每一行),则按 Sniffer.preferred 列出的顺序取优,与各分隔符出现次数无关。
在 Lib/csv.py 中该算法有更细致的工程化实现,可作为源码级佐证:
- 先调用
_parses_as_single_column排除整列加引号的"无分隔符"样本; - 对候选分隔符、引号字符(
"/')与转义字符(\)做笛卡尔积分组,惰性激活(_split_dormant)直到某引号字符真正出现在行首字段处; - 增量式窗口试解析(窗口 2000 字符起步、逐步 4 倍放大),配合
_eliminate_worse淘汰明显劣势的组合,控制整体重解析开销; - 用
_modal_share计算"众数字段数及其占比"作为一致性得分,辅以"带引号解析成功为直接证据"的加分与preferred次序的微调; - 最后分别用
_detect_doublequote、_detect_skipinitialspace、_detect_lineterminator补全其余参数。
从文档注释的版本变更可看到,sniff 的行为基于"试解析"推断,其结果可能与旧版 Python 不同;并且现在能探测 escapechar 与 lineterminator,请求的 delimiters 也不再限于 ASCII 字符。
has_header(sample)
分析样本(假定为 CSV 格式),若首行看起来像列标题则返回 True。判定基于每列的两类关键判据:
- 第 2 行及以后包含数值;
- 第 2 行及以后包含字符串,且其中至少一个值的长度与对应列首行(假想表头)长度不同。
首行之后最多采样 21 行,若超过半数的列/行组合满足判据则返回 True。原文档特意加注:这只是粗糙启发式,既可能误报也可能漏报。实现(has_header 方法)会先用 sniff(sample) 建 reader,对前 21 行逐列尝试 complex(...) 类型化、失败则退化为字符串长度,最后"投票"决定首行是否为表头。
preferred 属性
平局时优先采用的分隔符列表,按优先顺序排列且可修改,初始值为 [',', '\t', ';', ' ', ':']。注意 Lib/csv.py 中它是在 Sniffer.__init__ 里创建的实例属性。
Sniffer 官方使用示例(读入前 1024 字节探测后回到文件头正式解析):
with open('example.csv', newline='') as csvfile:
dialect = csv.Sniffer().sniff(csvfile.read(1024))
csvfile.seek(0)
reader = csv.reader(csvfile, dialect)
# ... process CSV file contents here ...
Lib/test/test_csv.py 的 TestSniffer 系列用例覆盖了 has_header 判真/判假、正则特殊字符作分隔符、21 行采样边界以及各种方言的嗅探正确性。
引用常量 QUOTE_*:控制引号何时生成与识别
模块定义了六个引用常量(C 层 QuoteStyle 枚举同名),分别作用于 writer 的"加引号时机"与 reader 的"引号解读方式":
| 常量 | writer 行为 | reader 行为 |
|---|---|---|
QUOTE_ALL |
所有字段都加引号 | 按引号规则解析 |
QUOTE_MINIMAL |
仅当字段含 delimiter、quotechar、'\r'、'\n' 或 lineterminator 中字符时才加引号;若 doublequote=False 且设置了 escapechar,则用转义而非加引号处理 quotechar |
默认解析 |
QUOTE_NONNUMERIC |
所有非数值字段加引号 | 所有未加引号字段转换为 float |
QUOTE_NONE |
永不加引号;输出中出现 delimiter、quotechar、escapechar、'\r'、'\n' 或 lineterminator 字符时以前置 escapechar 转义;未设 escapechar 时遇到需转义字符会抛 Error;可将 quotechar 设为 None 以禁止对其转义 |
对引号字符不做任何特殊处理 |
QUOTE_NOTNULL(3.12 新增) |
除 None 外的字段都加引号;None 写为空(不加引号)字符串 |
把空(未加引号)字段解读为 None,其余同 QUOTE_ALL |
QUOTE_STRINGS(3.12 新增) |
凡是字符串字段总是加引号;None 写为空字符串 |
把空字符串解读为 None,其余同 QUOTE_NONNUMERIC |
与常量直接对应的 C 实现可逐一在 Modules/_csv.c 中找到:writer 侧 csv_writerow_lock_held 的 switch (dialect->quoting) 里,QUOTE_NONNUMERIC 用 !PyNumber_Check(field) 判断是否加引号,QUOTE_ALL 恒为 1,QUOTE_STRINGS 用 PyUnicode_Check(field),QUOTE_NOTNULL 用 field != Py_None;reader 侧 parse_save_field 中,QUOTE_NOTNULL/QUOTE_STRINGS 模式下把空(未加引号)字段替换为 Py_None,而 QUOTE_NONNUMERIC/QUOTE_STRINGS 模式下对非空未加引号字段调用 PyNumber_Float 转成 float。此外 reader 状态机通过 unquoted_field 布尔标记区分字段是否被引号包裹。
需要注意:某些数值类型(如 bool、Fraction、IntEnum)的字符串表示无法转回 float,因而不能在 QUOTE_NONNUMERIC/QUOTE_STRINGS 读取模式下使用,这是原文档明确提醒的陷阱。
模块还定义一个异常:csv.Error,任何函数检测到错误时都会抛出它。
方言与格式化参数(Dialects and Formatting Parameters)
为简化输入输出记录格式的指定,相关格式化参数被归组为"方言"。方言是 Dialect 的子类;创建 reader/writer 时既可用字符串或 Dialect 子类作为 dialect 参数,也可额外或单独传入与下列属性同名的格式化参数来覆盖方言。
| 属性 | 默认值 | 含义与约束 |
|---|---|---|
delimiter |
',' |
单字符字段分隔符 |
doublequote |
True |
字段内出现 quotechar 时的处理:True 将其双写;False 用 escapechar 作前缀。输出时若 doublequote=False 且未设 escapechar,字段内发现 quotechar 会抛 Error |
escapechar |
None(禁用转义) |
writer 用它转义需转义字符:QUOTE_NONE 下转义 delimiter、quotechar、'\r'、'\n' 及 lineterminator 字符;doublequote=False 时转义 quotechar;还会转义它自身。读取时它使后续字符失去特殊含义。3.10 起 escapechar 本身也会被转义(此前会丢失);3.11 起不允许为空字符串 |
lineterminator |
'\r\n' |
writer 输出行的终止串。注意:reader 硬编码只认 '\r' 或 '\n' 为行尾,忽略此参数(未来可能改变) |
quotechar |
'"' |
单字符引用符,包裹含 delimiter、quotechar、换行字符的字段。可设为 None 以便在 QUOTE_NONE 下不转义 '"'。3.11 起不允许为空字符串 |
quoting |
默认 QUOTE_MINIMAL(若 quotechar 非 None),否则 QUOTE_NONE |
控制 writer 何时生成引号、reader 何时识别引号,取值为上节的 QUOTE_* 常量 |
skipinitialspace |
False |
为 True 时忽略紧随 delimiter 之后的空格。结合 delimiter=' ' 与 skipinitialspace=True 使用时不允许未加引号的空字段 |
strict |
False |
为 True 时对坏 CSV 输入抛 Error |
此外,方言支持 copy.replace(Dialect.__replace__),返回一个替换了指定格式化参数的新方言副本,见 Lib/csv.py 中的实现。
Reader 与 Writer 对象的公开接口
Reader 对象
reader() 返回的对象与 DictReader 实例共享以下公开成员:
csvreader.__next__():返回下一行——来自reader()则为 list,来自DictReader则为 dict,均按当前方言解析。通常用next(reader)调用;csvreader.dialect:解析器正在使用的方言(只读描述);csvreader.line_num:从源迭代器读取的行数。注意不等于返回的记录数,因为一条记录可能跨多行(例如带引号字段内嵌换行)。
DictReader 额外提供 fieldnames 属性:若构造时未传,则在首次访问或读取首条记录时初始化(见 DictReader.fieldnames 的惰性属性实现)。
Writer 对象
writer() 返回的对象与 DictWriter 实例共享以下公开成员,row 必须是可迭代对象:
csvwriter.writerow(row, /):把 row 按当前方言格式化后写入文件对象,返回底层write方法的返回值;3.5 起支持任意可迭代对象;csvwriter.writerows(rows, /):把 rows(row 的可迭代对象)全部写出;csvwriter.dialect:writer 使用的方言(只读描述)。
DictWriter 额外提供 writeheader()(3.2 新增,3.8 起返回内部 writerow 调用的返回值),按构造时的 fieldnames 写出表头行,其内部实现是把字段名做一次 dict(zip(fieldnames, fieldnames)) 自映射后交给 writerow。
writer 侧还有一个值得注意的底层行为:文档提示复数会被写成带括号的形式(如 (1+2j)),这可能给读取 CSV 的其他程序带来兼容问题。此外 C 层在写出空字段记录时若采用 QUOTE_NONE(或 QUOTE_STRINGS/QUOTE_NOTNULL 下的 None 字段)会抛出 "single empty field record must be quoted" 错误,因为这种记录无法被唯一解读。
综合示例:读取、写入、异常捕获与字符串解析
原文档的 Examples 一节给出了 5 类可直接运行的实战片段,本节逐一整理并补充说明。
最简读取:
import csv
with open('some.csv', newline='') as f:
reader = csv.reader(f)
for row in reader:
print(row)
读取替代格式(如 /etc/passwd 这类冒号分隔且无引号语义的文件):
import csv
with open('passwd', newline='') as f:
reader = csv.reader(f, delimiter=':', quoting=csv.QUOTE_NONE)
for row in reader:
print(row)
最简写入:
import csv
with open('some.csv', 'w', newline='') as f:
writer = csv.writer(f)
writer.writerows(someiterable)
指定非系统默认编码读写:由于 csv 读写的是文本层,编码由 open() 决定。用 open 读文件时默认按系统默认编码解码(见 locale.getencoding()),如需其他编码请显式传 encoding 参数:
import csv
with open('some.csv', newline='', encoding='utf-8') as f:
reader = csv.reader(f)
for row in reader:
print(row)
写入侧同理,只要在打开输出文件时指定 encoding 即可。
注册并使用新方言(把 /etc/passwd 风格固化成语名):
import csv
csv.register_dialect('unixpwd', delimiter=':', quoting=csv.QUOTE_NONE)
with open('passwd', newline='') as f:
reader = csv.reader(f, 'unixpwd')
进阶读取——捕获并报告错误(利用 reader.line_num 定位出错行):
import csv, sys
filename = 'some.csv'
with open(filename, newline='') as f:
reader = csv.reader(f)
try:
for row in reader:
print(row)
except csv.Error as e:
sys.exit(f'file {filename}, line {reader.line_num}: {e}')
解析内存字符串:模块虽不直接支持解析字符串,但由于 reader 接受"字符串的可迭代对象",一行即可实现:
import csv
for row in csv.reader(['one,two,three']):
print(row)
三个易错点小结
综合文档与源码,使用 csv 模块时最容易踩的坑集中在三处:
- 必须用
newline=''打开文件:否则引号字段内嵌换行解析错误,\r\n平台写入时会多出\r。由于模块自己做通用换行处理,总是显式传newline=''是安全做法; - 读取端忽略
lineterminator:reader 硬编码识别'\r'/'\n'为行尾,lineterminator只影响 writer 输出,二者语义不对称; - 引号与转义的组合限制:
doublequote=False且无escapechar时字段内出现quotechar会抛错;QUOTE_NONE下遇到需转义字符而未设escapechar同样抛错;quotechar/escapechar在 3.11 起不允许为空字符串;bool/Fraction/IntEnum不适用于QUOTE_NONNUMERIC/QUOTE_STRINGS的读取模式。
如果想深入验证文中所述的各类行为,可以研读模块级回归测试 Lib/test/test_csv.py(含 Sniffer、field_size_limit、各 QUOTE 常量组合的大量用例)以及 C 加速层源码 Modules/_csv.c,两者是官方文档之外最可靠的"事实来源"。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0625
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00