现象
abses/space/patch.py 是全包最大的模块(约 1000 行),PatchModule 一个类就有 37 个方法,混了六类职责;其中 __init__ 是一个 200 行、25 个分支 的五态构造器。
证据
__init__ 按五种互斥入参分派:
入参
含义
raster_file
从栅格文件建
vector_file
从矢量文件 / GeoDataFrame 建
xda
从 xarray DataArray 建
source_layer
复制已有图层
shape + resolution
从形状与分辨率建
五个分支互不共享状态,最后都汇到同一个 RasterLayer.__init__ 调用。签名里挤了 15 个关键字参数,docstring 只好用 # Resolution-based creation parameters 这种注释来分组——这本身就是「这里应该是五个函数」的信号。
分支里还有三处逐字重复的向后兼容块:
# In 0.7.x, providing attr_name automatically applied the raster data
if attr_name is not None :
apply_raster = True
出现在 raster / vector / xarray 三个分支里,一模一样。
类的六类职责:
职责
大致范围
A. 构造 / 栅格读入
__init__
B. mesa-geo 兼容补丁
_update_transform、_initialize_cells、__getattr__
C. 格子存储 / 缓存 / 索引
cells_lst、mask、cells、array_cells、coords、__getitem__、coord_iter
D. 栅格属性 I/O
_attr_or_array、dynamic_var、get_xarray、_add_attribute、_add_dataarray、apply_raster、get_raster、reproject
E. 空间查询 / 选择
_select_by_geometry、select/sel、get_neighboring_cells、get_neighboring_by_indices、indices_out_of_bounds、transform_coord
F. 主体聚合统计
apply、count_agents、apply_agents(apply_agents 一个方法 106 行、含 3 个嵌套闭包)
建议的切分顺序(按风险从低到高)
A → abses/space/patch_factory.py :五个自由函数 from_raster_file() / from_vector() / from_xarray() / from_layer() / from_shape(),各自返回统一的 (width, height, crs, total_bounds, xda);__init__ 收缩成约 30 行的分派器。这是最干净的缝——五个分支零耦合。顺带消掉上面那三份重复的兼容块。
F → 独立模块或 PatchModule.agents 访问器 :count_agents / apply_agents 本质上是 (module, agent_type/func) 的纯函数。(注意 apply_agents 的静默吞异常问题另见 空间热路径四处静默错误:整数栅格掩膜失效 / apply_raster 无 else / mask 缓存不失效 / apply_agents 吞异常成 NaN #153 。)
D → 一个 RasterAttributeStore 协作对象 :这组方法只操作 self._attributes + self.array_cells,是个闭合集合。
E → abses/space/selection.py :select 已经把一半工作委托给了 cells_lst.select,等于半抽出来了。
B 和 C 建议留在 patch.py——那才是真正的 mesa-geo 集成面。
顺带需要处理的两个缺陷
绑定方法上的 lru_cache :
@functools .lru_cache (maxsize = 1000 )
def get_neighboring_by_indices (self , indices , moore , ...):
缓存挂在类 上、键里含 self,会为整个进程强引用最多 1000 个 PatchModule(Experiment 重复运行下就是内存泄漏),而且把同一个可变 ActorsList 发给所有调用方。建议改用实例级缓存并返回不可变副本。
用 assert 做输入校验 :构造器路径上有 assert isinstance(resolution, ...)、assert width is None and height is None 等。python -O 下会被剥掉,公开构造器不该依赖它。改成显式 raise ValueError/TypeError。
验收标准
现象
abses/space/patch.py是全包最大的模块(约 1000 行),PatchModule一个类就有 37 个方法,混了六类职责;其中__init__是一个 200 行、25 个分支的五态构造器。证据
__init__按五种互斥入参分派:raster_filevector_filexdasource_layershape+resolution五个分支互不共享状态,最后都汇到同一个
RasterLayer.__init__调用。签名里挤了 15 个关键字参数,docstring 只好用# Resolution-based creation parameters这种注释来分组——这本身就是「这里应该是五个函数」的信号。分支里还有三处逐字重复的向后兼容块:
出现在 raster / vector / xarray 三个分支里,一模一样。
类的六类职责:
__init___update_transform、_initialize_cells、__getattr__cells_lst、mask、cells、array_cells、coords、__getitem__、coord_iter_attr_or_array、dynamic_var、get_xarray、_add_attribute、_add_dataarray、apply_raster、get_raster、reproject_select_by_geometry、select/sel、get_neighboring_cells、get_neighboring_by_indices、indices_out_of_bounds、transform_coordapply、count_agents、apply_agents(apply_agents一个方法 106 行、含 3 个嵌套闭包)建议的切分顺序(按风险从低到高)
abses/space/patch_factory.py:五个自由函数from_raster_file()/from_vector()/from_xarray()/from_layer()/from_shape(),各自返回统一的(width, height, crs, total_bounds, xda);__init__收缩成约 30 行的分派器。这是最干净的缝——五个分支零耦合。顺带消掉上面那三份重复的兼容块。PatchModule.agents访问器:count_agents/apply_agents本质上是(module, agent_type/func)的纯函数。(注意apply_agents的静默吞异常问题另见 空间热路径四处静默错误:整数栅格掩膜失效 / apply_raster 无 else / mask 缓存不失效 / apply_agents 吞异常成 NaN #153。)RasterAttributeStore协作对象:这组方法只操作self._attributes+self.array_cells,是个闭合集合。abses/space/selection.py:select已经把一半工作委托给了cells_lst.select,等于半抽出来了。B 和 C 建议留在
patch.py——那才是真正的 mesa-geo 集成面。顺带需要处理的两个缺陷
绑定方法上的
lru_cache:缓存挂在类上、键里含
self,会为整个进程强引用最多 1000 个PatchModule(Experiment重复运行下就是内存泄漏),而且把同一个可变ActorsList发给所有调用方。建议改用实例级缓存并返回不可变副本。用
assert做输入校验:构造器路径上有assert isinstance(resolution, ...)、assert width is None and height is None等。python -O下会被剥掉,公开构造器不该依赖它。改成显式raise ValueError/TypeError。验收标准
PatchModule.__init__缩到 50 行以内。patch.py82% 的覆盖率只由tests/api/test_nature.py间接支撑,没有专门的测试文件)。get_neighboring_by_indices不再跨模型实例持有引用。assert全部换成显式异常。