news 2026/9/20 10:17:22

NumPy 2.0 迁移指南:Python 与 C API 全面升级实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
NumPy 2.0 迁移指南:Python 与 C API 全面升级实战
  • 科学计算
  • 数据分析

【免费下载链接】numpy

The fundamental package for scientific computing with Python.

项目地址:https://gitcode.com/gh_mirrors/nu/numpy
点击查看免费下载

导读

NumPy 2.0 是一次具有里程碑意义的大版本升级,它同时破坏了 Python API 与 C API 的向后兼容性。本文基于本仓库的官方迁移指南(doc/source/numpy_2_0_migration_guide.rst),系统梳理了从 NumPy 1.x 迁移到 2.0 的全部要点:包括 NEP 50 标量提升规则变化、Windows 平台默认整数类型调整、PyArray_Descr结构体不透明化、复数类型底层 C99 化、以及主命名空间中约 100 个成员的移除与迁移方案。读完本文,你将掌握用 Ruff 的 NPY201 规则自动化扫描代码、手工适配 Python 与 C/Cython 扩展、以及编写同时兼容 1.x 与 2.x 代码的全部实战技巧。

注意:NumPy 2.0 同时破坏了二进制兼容性。如果你在分发依赖 NumPy C API 的 Python 包二进制文件,请务必阅读本文 C-API 变更 一节中关于 ABI 处理的内容。


一、用 Ruff 插件自动迁移:规则 NPY201

迁移工作量大、易遗漏,好在社区已经提供了自动化工具。2.0 发布说明与本文迁移指南中覆盖的大部分变更,都可以用 Ruff 的专用规则NPY201numpy2-deprecation)自动适配到下游代码中。

安装与配置要求:需要ruff>=0.4.8,并在pyproject.toml中加入规则:

[tool.ruff.lint] select = ["NPY201"]

也可以在命令行直接对指定代码目录执行检查:

$ ruff check path/to/code/ --select NPY201

NPY201 会识别本文后面提到的绝大部分废弃成员(如np.alltruenp.cumproductnp.Inf等),并给出对应的迁移建议,是迁移到 2.0 的第一道自动化防线。


二、数据类型提升规则变更(NEP 50)

NumPy 2.0 按照 NEP 50(scalar promotion) 改变了不同数据类型组合时的提升(promotion)规则,详见 NEP 原文中的变更示例表与向后兼容性章节。

2.1 最大的兼容性破坏:标量精度被一致保留

最大的向后兼容性变更在于:标量(scalar)的精度现在会被一致地保留。两个典型例子:

  • np.float32(3) + 3.现在返回float32,而之前返回 float64;
  • np.array([3], dtype=np.float32) + np.float64(3)现在返回float64数组(标量的更高精度不再被忽略)。

对浮点数而言,与标量混合运算时可能产生更低精度的结果;对整数而言,则可能出现错误或溢出

2.2 解决方案

  • 显式转换(cast):在混合运算时显式指定 dtype。
  • 回归 Python 标量:很多时候最佳方案是用int()float()numpy_scalar.item()确保操作的是 Python 标量,从而避免 NumPy 标量参与提升。

2.3 用警告追踪行为变化

你可以在测试期间开启“weak promotion + warning”模式,让发生行为变化的运算发出警告,甚至用warnings.simplefilter将其升级为异常以便获取回溯:

np._set_promotion_state("weak_and_warn")

该模式对测试阶段定位受影响代码很有用。需要注意的是,运行它可能会标记出许多实际无关紧要的变更,需要结合人工判断筛选。


三、Windows 平台默认整数变更

3.1 变更内容

NumPy 的默认整数类型现在在所有 64 位系统上为64 位(32 位系统上为 32 位)。出于 Python 2 时代的历史原因,之前默认整数等价于 C 的long类型;现在默认整数等价于np.intp

3.2 对用户的影响

大多数最终用户不受影响:部分操作会多占一些内存,但部分操作反而可能更快。如果你因为调用某个编译语言编写的库而遇到问题,可以显式转换回long

arr = arr.astype("long", copy=False)

3.3 对扩展库开发者的影响

用 C、Cython 或类似语言对接编译代码的库,如果 C 侧使用了long或等价类型,可能需要更新以适配用户输入。此时建议:

  • 改用intp并对用户输入做转换;
  • 或同时支持longintp(以更好兼容 NumPy 1.x)。

在 C 或 Cython 中创建新整数数组时,新的NPY_DEFAULT_INT宏会根据 NumPy 版本展开为NPY_LONGNPY_INTP。仓库中该宏的定义位于 numpy/_core/include/numpy/npy_2_compat.h:在NPY_FEATURE_VERSION >= NPY_2_0_API_VERSION时定义为NPY_INTP,在 1.x 下定义为NPY_LONG,而当同时兼容 1.x 与 2.x 编译时则采用运行时判断:

#define NPY_DEFAULT_INT \ (PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION ? NPY_INTP : NPY_LONG)

注意:NumPy 的 random API 不受此变更影响。


四、C-API 变更

2.0 移除或替换了一些过时、难以维护的定义,部分新 API 定义在 1.x 与 2.0 之间的运行时求值结果不同。这些定义集中在 numpy/_core/include/numpy/npy_2_compat.h,该头文件可以被整体或部分 vendoring(复制)到下游项目,以便在针对 NumPy 1.x 编译时也能获得这些定义。

需要针对 1.x 和 2.0 实现不同行为时,可用运行时判断:

if (PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION) { /* 2.0 行为 */ }

(compat 头文件已按此用法做了兼容性定义。)如果你还需要其他 workaround,可以向 NumPy 社区反馈。

4.1PyArray_Descr结构体已改变

这是影响最大的 C-API 变更之一:PyArray_Descr结构体变得更加不透明(opaque),以便未来增加新的 flags、让 itemsize 不再受int大小限制、改进结构化 dtype,并避免新 dtype 背负旧字段负担。

影响范围

  • 只使用类型编号(type number)等初始字段的代码不受影响;
  • 大多数代码主要通过->elsize访问字段——当 descriptor 附着在数组上时(如arr->descr->elsize),最佳替换方案是PyArray_ITEMSIZE(arr)
  • 无法替换时,必须使用新的访问器函数:
访问器说明
PyDataType_ELSIZE/PyDataType_SET_ELSIZE元素大小(结果类型从int变为npy_intp
PyDataType_ALIGNMENT对齐要求
PyDataType_FIELDS/PyDataType_NAMES/PyDataType_SUBARRAY结构化 dtype 的字段、名称与子数组
PyDataType_C_METADATAC 侧元数据

在仓库源码中,这些访问器由DESCR_ACCESSOR宏批量生成,见 numpy/_core/include/numpy/npy_2_compat.h,例如:

DESCR_ACCESSOR(ELSIZE, elsize, npy_intp, 0) DESCR_ACCESSOR(ALIGNMENT, alignment, npy_intp, 0) DESCR_ACCESSOR(SUBARRAY, subarray, PyArray_ArrayDescr *, 1) DESCR_ACCESSOR(NAMES, names, PyObject *, 1) DESCR_ACCESSOR(FIELDS, fields, PyObject *, 1) DESCR_ACCESSOR(C_METADATA, c_metadata, NpyAuxData *, 1)

其中第 4 个参数(legacy_only)为 1 的访问器只在 legacy dtype 上有效,非 legacy dtype 会返回 0。同时注意PyDataType_SET_ELSIZE现在接收npy_intp参数,且同头文件还提供了PyDataType_ISLEGACY(dtype)等判断辅助。

Cython 用户:应使用 Cython 3,此时该变更对你是透明的(仅针对 NumPy 2 编译时,结构体访问对elsizealignment依然可用)。

同时兼容 1.x 与 2.x 编译:若使用这些新访问器,需要二选一:

  1. 用宏在本地定义(1.x 下直接字段访问):
#if NPY_ABI_VERSION < 0x02000000 #define PyDataType_ELSIZE(descr) ((descr)->elsize) #endif
  1. npy_2_compat.h加入代码库,并在针对 NumPy 1.x 编译时显式包含它(这些访问器属于新 API;在 NumPy 2 下包含该文件没有任何副作用)。

自定义用户 DType:现有用户 dtype 必须改用PyArray_DescrProto来定义 dtype,并小幅修改代码,详见PyArray_RegisterDataType的说明。npy_2_compat.h在 1.x 编译环境下会将PyArray_DescrProto别名为PyArray_Descr(numpy/_core/include/numpy/npy_2_compat.h),从而平滑过渡。

4.2 功能移至需要import_array()的头文件

如果你之前只包含了ndarraytypes.h,会发现部分功能不再可用,需要包含ndarrayobject.h或类似头文件。在把npy_2_compat.hvendoring 到自己代码库时,该包含同样必要,以便针对 NumPy 1.x 编译时也能使用新定义。

此前不需要 import 的功能包括:

  • 访问 dtype flags 的函数:PyDataType_FLAGCHKPyDataType_REFCHK,以及相关的NPY_BEGIN_THREADS_DESCR
  • PyArray_GETITEMPyArray_SETITEM

这些宏在 numpy/_core/include/numpy/ndarrayobject.h 中可以看到与PyDataType_ELSIZEPyDataType_FLAGS的联动实现,例如:

#define PyDataType_FLAGCHK(dtype, flag) \ (PyDataType_FLAGS(dtype) & (flag)) #define PyDataType_REFCHK(dtype) \ PyDataType_FLAGCHK(dtype, NPY_ITEM_REFCOUNT)

警告:使用npy_2_compat.h头文件时,必须通过import_array()机制确保完整 NumPy API 可访问。大多数扩展模块应该已经调用过它。如果还没有,NumPy 新增了PyArray_ImportNumPyAPI()作为更推荐的方式——该函数可重复轻量调用,因此可以在任何需要的位置插入(如果你想避免在模块导入时一次性设置)。其实现见 numpy/_core/include/numpy/npy_2_compat.h,本质上是PyArray_API == NULL时的惰性import_array1(-1)

4.3 最大维度数增加到 64

最大维度数(及参数个数)增加到64,影响NPY_MAXDIMSNPY_MAXARGS宏。建议:

  • 审查这两个宏的使用;
  • 尽量不要使用它们(尤其是NPY_MAXARGS),以便未来版本能移除维度数量限制。

NPY_MAXDIMS此前还被用作 C-API 中axis=None的哨兵值,包括PyArray_AxisConverter。现在后者返回-2147483648(最小的整数值)作为 axis。其他函数可能报错AxisError: axis 64 is out of bounds for array of dimension,此时应改用NPY_RAVEL_AXIS而不是NPY_MAXDIMS

NPY_RAVEL_AXIS定义在npy_2_compat.h中且运行时依赖:在 NumPy 1.x 上映射为 32,在 NumPy 2.x 上映射为-2147483648(numpy/_core/include/numpy/npy_2_compat.h):

#if NPY_FEATURE_VERSION >= NPY_2_0_API_VERSION #define NPY_RAVEL_AXIS NPY_MIN_INT /* 2.0: -2147483648 */ #define NPY_MAXARGS 64 #elif NPY_ABI_VERSION < 0x02000000 #define NPY_RAVEL_AXIS 32 /* 1.x: 32 */ #define NPY_MAXARGS 32 #else #define NPY_RAVEL_AXIS \ (PyArray_RUNTIME_VERSION >= NPY_2_0_API_VERSION ? NPY_MIN_INT : 32) #endif

4.4 复数类型:底层类型变更

所有复数类型的底层 C 类型改为原生 C99 类型。虽然内存布局与 NumPy 1.x 使用的类型完全一致,但 API 略有不同:不能直接字段访问(如c.realc.imag)。

推荐做法

  • 读取实部/虚部:使用npy_crealnpy_cimag(以及对应的 float、long double 变体npy_crealf/npy_cimagf/npy_creall/npy_cimagl),这些函数在 1.x 与 2.x 下都能工作;
  • 设置实部/虚部:使用新增的npy_csetrealnpy_csetimag,以及兼容宏NPY_CSETREALNPY_CSETIMAG(同样有 float、long double 变体)。

这些函数定义在 numpy/_core/include/numpy/npy_math.h,宏形式则定义在 numpy/_core/include/numpy/npy_2_complexcompat.h,例如:

static inline double npy_creal(const npy_cdouble z); static inline void npy_csetreal(npy_cdouble *z, const double r); #define NPY_CSETREAL(z, r) npy_csetreal(z, r) #define NPY_CSETIMAG(z, i) npy_csetimag(z, i)

在 C++ 下,底层类型仍然是 struct(以上所有内容依然有效)。

对 Cython 的影响

  • 推荐始终使用原生 typedefcfloat_tcdouble_tclongdouble_t,而不是 NumPy 类型npy_cfloat等——除非你必须对接用 NumPy 类型编写的 C 代码;
  • 使用原生 typedef 时仍可写c.realc.imag属性,但在 Cython 的 C++ 模式下不能再使用就地运算符(如c.imag += 1)。

I宏冲突:因为 NumPy 2 现在包含complex.h,使用名为I的变量的代码可能报错:

error: expected ')' before '__extension__' double I,

要使用名字I,现在必须#undef I。(附注:NumPy 2.0.1 曾短暂加入#undef I以帮助尚未包含complex.h的用户。)


五、命名空间变更

NumPy 2.0 移除了部分函数、模块和常量,以让命名空间更友好:清除过时功能、明确哪些部分属于私有。详情见 NEP 52(Python API cleanup)。对大多数变更而言,迁移方式就是替换为向后兼容的替代品。

5.1 主命名空间(np

主命名空间np中约有100 个成员被废弃、移除或移动。下表是被移除的成员及其迁移指引:

被移除成员迁移指引
add_docstring仍可通过np.lib.add_docstring访问
add_newdoc仍可通过np.lib.add_newdoc访问
add_newdoc_ufunc内部函数,无替代品
alltrue改用np.all
asfarray改用带浮点 dtype 的np.asarray
byte_bounds现在位于np.lib.array_utils.byte_bounds
cast改用np.asarray(arr, dtype=dtype)
cfloat改用np.complex128
charrarray仍可通过np.char.chararray访问
clongfloat改用np.clongdouble
compare_chararrays仍可通过np.char.compare_chararrays访问
compat无替代品(不再支持 Python 2)
complex_改用np.complex128
cumproduct改用np.cumprod
DataSource仍可通过np.lib.npyio.DataSource访问
deprecate直接用warnings.warnDeprecationWarning,或用typing.deprecated
deprecate_with_doc直接用warnings.warnDeprecationWarning,或用typing.deprecated
disp使用你自己的打印函数
fastCopyAndTranspose改用arr.T.copy()
find_common_type改用numpy.promote_typesnumpy.result_type;要实现scalar_types参数的语义,可对numpy.result_type传入 Python 值00.00j
format_parser仍可通过np.rec.format_parser访问
get_array_wrap(无指引,随旧实现移除)
float_改用np.float64
geterrobj改用np.errstate上下文管理器
Inf改用np.inf
Infinity改用np.inf
infty改用np.inf
issctype改用issubclass(rep, np.generic)
issubclass_改用内置issubclass
issubsctype改用np.issubdtype
mat改用np.asmatrix
maximum_sctype使用具体的 dtype;应避免任何隐式机制,在代码中显式选择某一种类的最大 dtype
NaN改用np.nan
nbytes改用np.dtype(<dtype>).itemsize
NINF改用-np.inf
NZERO改用-0.0
longcomplex改用np.clongdouble
longfloat改用np.longdouble
lookfor直接搜索 NumPy 文档
obj2sctype改用np.dtype(obj).type
PINF改用np.inf
product改用np.prod
PZERO改用0.0
recfromcsv改用带逗号分隔符的np.genfromtxt
recfromtxt改用np.genfromtxt
round_改用np.round
safe_eval改用ast.literal_eval
sctype2char改用np.dtype(obj).char
sctypes直接显式访问 dtype
seterrobj改用np.errstate上下文管理器
set_numeric_ops一般情况改用PyUFunc_ReplaceLoopBySignature;对 ndarray 子类,定义__array_ufunc__方法并覆盖相关 ufunc
set_string_function改用np.set_printoptions的 formatter 参数自定义 NumPy 对象打印
singlecomplex改用np.complex64
string_改用np.bytes_
sometrue改用np.any
source改用inspect.getsource
tracemalloc_domain现在从np.lib获取
unicode_改用np.str_
who使用 IDE 的变量浏览器或locals()

如果上表没有列出你使用过且在 2.0 被移除的成员,说明它是私有成员。你应该改用现有 API;若不可行,可以向 NumPy 社区请求恢复。

下表是被**废弃(deprecated)**的成员,将在 2.0 之后的某个版本移除:

废弃成员迁移指引
in1d改用np.isin
row_stack改用np.vstackrow_stack本来就是vstack的别名)
trapz改用np.trapezoidscipy.integrate的函数

另外,一组内部枚举也被移除。由于它们在下游库中没有被使用,官方不提供替换指引:

FLOATING_POINT_SUPPORTFPE_DIVIDEBYZEROFPE_INVALIDFPE_OVERFLOWFPE_UNDERFLOWUFUNC_BUFSIZE_DEFAULTUFUNC_PYVALS_NAMECLIPWRAPRAISEBUFSIZEALLOW_THREADSMAXDIMSMAY_SHARE_EXACTMAY_SHARE_BOUNDS

5.2numpy.lib命名空间

np.lib中的大部分函数同时存在于主命名空间(那是它们的主要位置)。为明确每个公共函数的访问方式,np.lib现在基本清空,只保留少数专用子模块、类与函数:

  • 子模块:array_utilsformatintrospectmixinsnpyioscimathstride_tricks
  • 类:ArrayteratorNumpyVersion
  • 函数:add_docstringadd_newdoc
  • 常量:tracemalloc_domain

如果在np.lib访问属性得到AttributeError,先尝试从主np命名空间访问。若主命名空间也没有,说明你用的是私有成员——应改用现有 API 或向社区请求恢复。

5.3numpy.core命名空间

np.core现在正式成为私有命名空间,并更名为np._core。用户绝不应直接从_core取成员,而应通过主命名空间访问。_core模块的布局未来可能不通知地改变——这与遵循废弃周期策略的公共模块不同。如果主命名空间也没有你要的成员,同样应改用现有 API 或请求恢复。

5.4 ndarray 与标量方法

np.ndarraynp.generic标量类的少数方法被移除:

过期成员迁移指引
newbyteorder改用arr.view(arr.dtype.newbyteorder(order))
ptp改用np.ptp(arr, ...)
setitem改用arr[index] = value

5.5numpy.strings命名空间

NumPy 2.0 新建了numpy.strings命名空间,其中大部分字符串操作以ufunc形式实现。旧的numpy.char命名空间仍然可用,并尽可能复用新 ufunc 以获得更高性能。官方推荐今后使用numpy.strings中的函数,numpy.char可能在将来被废弃。


六、其他重要变更

6.1 关于 pickle 文件的兼容性

  • NumPy 2.0 设计上可以加载 NumPy 1.26 创建的 pickle 文件,反之亦然;
  • 对于 1.25 及更早版本,加载 NumPy 2.0 的 pickle 文件会抛出异常。

6.2 适配copy关键字行为变更

2.0 中np.asarraynp.arrayndarray.__array__copy关键字行为发生了变化,可能需要如下调整:

  • 大多数使用np.array(..., copy=False)的代码可以改为np.asarray(...)。旧代码之所以这样写,是因为它比默认“按需复制”的np.asarray开销更小;如今这个前提已不成立,np.asarray是更推荐的选择;
  • 对于需要显式传递None/False表达“按需复制”且同时兼容 1.x 与 2.x 的代码,可以参考 SciPy 的 PR 示例做法(scipy#20172)实现版本分派;
  • 对于任何非 NumPy 数组类对象的__array__方法,签名中必须加入dtype=Nonecopy=None关键字——这在旧版 NumPy 下也能工作(旧版只是永远不会传入copy)。加入关键字后语义为:
    • copy=True且任意dtype值:总是返回新副本;
    • copy=None:仅在必要时(例如受dtype驱动)创建副本;
    • copy=False:绝不创建副本;若需要副本才能返回 NumPy 数组或满足dtype,则抛出异常(ValueError)。

6.3 编写依赖 NumPy 版本的代码

大多数情况下无需显式分支判断numpy版本——代码可以同时兼容 1.x 和 2.0。但若确有必要,推荐使用numpy.lib.NumpyVersion(定义于 numpy/lib/_version.py,实现了完整的版本字符串比较语义,能正确处理 release candidate 等版本):

# 示例:AxisError 在 2.0 中不再位于主命名空间, # 而在 <1.25.0 中也不存在于 exceptions 命名空间 # (此处以 <2.0.0b1 为例演示): if np.lib.NumpyVersion(np.__version__) >= '2.0.0b1': from numpy.exceptions import AxisError else: from numpy import AxisError

该模式能正确处理 NumPy 的 release candidate 版本,这在 2.0.0 发布周期内尤为重要。


七、迁移路线图总结

  1. 自动化扫描:安装ruff>=0.4.8,用NPY201规则扫描全部代码,逐项修复主命名空间的废弃成员;
  2. 测试期排查提升变化:在测试套件中临时开启np._set_promotion_state("weak_and_warn"),结合warnings.simplefilter("error")定位受 NEP 50 影响的运算;
  3. 检查平台相关代码:确认默认整数(Windows 上从long变为intp)不会破坏与编译库的互操作;
  4. C/Cython 扩展:重点审查PyArray_Descr直接字段访问(改用访问器或PyArray_ITEMSIZE)、复数字段访问(改用npy_creal/npy_csetreal等)、NPY_MAXDIMS哨兵用法(改用NPY_RAVEL_AXIS)、import_array()包含关系,以及用户自定义 dtype 的PyArray_DescrProto适配;
  5. 命名空间与 API 替换:按上文的三个表格逐项替换被移除、废弃与过期的成员;
  6. 二进制分发:将 numpy/_core/include/numpy/npy_2_compat.h(必要时连同 npy_2_complexcompat.h)vendoring 进项目,确保针对 1.x 编译的二进制也能在 2.0 运行时正确工作;
  7. 回归测试:用 NumPy 1.26 与 2.0 双版本跑通测试,确认 pickle 互读与copy关键字语义符合预期。

如果你在迁移过程中遇到本指南未覆盖的场景、或现有访问器函数不足以解决问题,欢迎向 NumPy 提交 issue,社区会继续补充 workaround 与兼容方案。

  • 科学计算
  • 数据分析

【免费下载链接】numpy

The fundamental package for scientific computing with Python.

项目地址:https://gitcode.com/gh_mirrors/nu/numpy
点击查看免费下载
上一篇:GPU像素级对象选取:ShapeUp着色器编码点击拾取技巧全解析
下一篇:洛雪音乐音源配置终极指南:5步实现全平台无损音乐播放

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 10:17:10

MEMS 3D相机设计全解析:结构光编码与三维重建实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 10:16:50

Quasar CSS 定位工具类完全指南:fixed / absolute / 对齐与垂直居中

Quasar CSS 定位工具类完全指南&#xff1a;fixed / absolute / 对齐与垂直居中 【免费下载链接】quasar Quasar Framework - Build high-performance VueJS user interfaces in record time 项目地址: https://gitcode.com/gh_mirrors/qu/quasar Quasar 框架内置了一套…

作者头像 李华
网站建设 2026/9/20 10:16:21

Podman Desktop启动报rdclientax.dll错误的根因与绕过方案

1. 问题现场还原&#xff1a;不是“启动失败”&#xff0c;而是“远程桌面控件加载中断”第一次在 Windows 10 22H2 上双击 Podman Desktop 图标&#xff0c;看到的不是熟悉的容器管理界面&#xff0c;而是一个弹窗&#xff0c;标题栏写着“Podman Desktop — 错误”&#xff0…

作者头像 李华
网站建设 2026/9/20 10:14:34

Flutter与HarmonyOS跨端游戏控制开发实践

1. 项目概述&#xff1a;Flutter与HarmonyOS的跨端游戏控制实践在移动应用开发领域&#xff0c;跨平台技术正在重塑开发者的工作方式。作为一名长期从事移动开发的工程师&#xff0c;我发现Flutter与HarmonyOS的结合为游戏开发带来了全新的可能性。这次我们要实现的贪吃蛇游戏控…

作者头像 李华