避免 Python 中 GDAL/OGR 绑定导致的崩溃:一个所有权问题
避免 Python 中 GDAL/OGR 绑定导致的崩溃:一个所有权问题
ytkz在使用 OGR 和 GDAL 的 Python 绑定时,有时你可能会遇到一个奇怪的现象:同样的代码结构,在某些情况下正常运行,而在其他情况下却导致 Python 闪退崩溃。本文将解释这种现象的原因,并演示如何避免这种问题。
问题重现
假设我们正在使用一个 shapefile 文件,并尝试从中提取几何图形信息:
from osgeo import ogr
shp_ds = ogr.Open(r'D:\gadm\France\gadm41_FRA_0.shp')
lyr = shp_ds.GetLayer(0)
lyr.GetFeature(0).GetGeometryRef().GetX() # 这一行可能导致崩溃
结果会发生闪退。
上面的代码在执行时可能导致 Python 闪退。通过修改代码,问题似乎得到解决:
from osgeo import ogr
shp_ds = ogr.Open(r'D:\gadm\France\gadm41_FRA_0.shp')
lyr = shp_ds.GetLayer(0)
feature = lyr.GetFeature(0)
feature.GetGeometryRef().GetX() # 这次不会崩溃
这次的结果不会闪退。
问题分析
这种崩溃背后的原因与 GDAL 和 OGR 的对象生命周期管理有关。在 GDAL/OGR 的 Python 绑定中,当 Python 删除一个对象时,背后的 C++ 对象会随之被销毁。如果该 C++ 对象还拥有其他子对象的所有权(例如,dataset
对象包含 band
,feature
对象包含 geometry
),那么当父对象被删除后,子对象也会被销毁。如果 Python 中还保留着子对象的引用,访问该对象时就会导致崩溃。
这是因为 OGR/GDAL 绑定对这些 C++ 对象的引用关系没有进行足够的保护,可能会导致访问已被销毁的对象。
举个类似的例子:
from osgeo import gdal
dataset = gdal.Open('C:\\RandomData.img')
band = dataset.GetRasterBand(1)
print(band.Checksum()) # 正常工作,输出 31212
在上面的代码中,band
对象依赖于 dataset
对象。如果我们删除 dataset
后再尝试使用 band
,将会出现错误:
from osgeo import gdal
dataset = gdal.Open('C:\\RandomData.img')
band = dataset.GetRasterBand(1)
del dataset
band.Checksum() # 抛出 TypeError
在 GDAL 3.7 及更早的版本中,删除 dataset
后再使用 band
甚至可能导致 Python 崩溃,而不是抛出异常。
如何避免
为了避免这种问题,确保在使用子对象时,父对象的引用仍然存在。比如,在上面的 OGR 示例中,通过将 lyr.GetFeature(0)
的返回值赋给一个变量,可以确保 lyr
和 shp_ds
在 feature
使用时不会被销毁。
正确的方式:
shp_ds = ogr.Open(r'D:\gadm\France\gadm41_FRA_0.shp')
lyr = shp_ds.GetLayer(0)
feature = lyr.GetFeature(0) # 确保 feature 对应的 lyr 仍然存在
feature.GetGeometryRef().GetX()
总结
GDAL 和 OGR 的 Python 绑定中的对象生命周期管理可能会导致 Python 崩溃,尤其是当父对象在子对象仍在使用时被销毁。通过小心管理对象引用,可以避免此类崩溃问题。