You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python设置GObject派生类属性时Runtime Warning问题排查

问题:Python中直接赋值GObject属性触发RuntimeWarning

背景

我开发了一款基于GLib和GObject的C库,用于心理学实验,目标是生成Python/JavaScript等语言绑定,方便心理学/语言学研究者使用。C语言的性能满足毫秒级精度呈现刺激的需求,GObject Introspection生成的绑定使用体验也不错。

问题现象

在Python中直接通过属性赋值(如circle.props.color = Psy.Color(r=0.5, g=0.5, b=0.5))设置GObject派生类的属性时,会触发Runtime Warning;但使用setter方法(instance.set_property_name(Psy.SomeNewObject()))或先将对象赋值给变量再设置属性则无异常。

测试Python代码

#!/usr/bin/env python

import gi
import tempfile

# All object in Psy.* derive from GObject and not GObject.InitiallyUnowned.
# Hence, they cannot have a floating reference. All objects/functions (should) use
# transfer annotations, to determine object lifetime, as is currently advised.

gi.require_version("Psy", "0.1")
from gi.repository import Psy

bg_color = Psy.Color(r=0.5, g=0.5, b=0.5)
fg_color = Psy.Color(r=1.0, g=0.0, b=0.0)

x, y = 0.0, 0.0
radius = 150.0
num_vertices = 100

canvas = Psy.GlCanvas.new(640, 480)
circle = Psy.Circle.new_full(canvas, x, y, num_vertices, radius)
canvas.set_background_color(bg_color)  # 无异常
circle.props.color = fg_color  # 无异常,Python持有引用
# 以下代码会触发警告,需要修复!
circle.props.color = Psy.Color(r=0.5, g=0.5, b=0.5)
circle.props.color = fg_color
circle.play_for(
    canvas.get_time().add(Psy.Duration.new_ms(16)), Psy.Duration.new_ms(50)
)  # 绘制圆形50毫秒
canvas.iterate()
image = canvas.get_image()
image.save_path(tempfile.gettempdir() + "/red-circle.png", "png")

触发的警告信息

sys:1: RuntimeWarning: Expecting to marshal a borrowed reference for
<Psy.Color object at 0x7f9e0ee26a80 (PsyColor at 0x5616651dba00)>, but
nothing in Python is holding a reference to this object.

C库相关实现代码

Circle类继承自PsyVisualStimulus抽象类,以下是PsyVisualStimulus的关键代码:

// header

#define PSY_TYPE_VISUAL_STIMULUS psy_visual_stimulus_get_type()
G_DECLARE_DERIVABLE_TYPE(
    PsyVisualStimulus, psy_visual_stimulus, PSY, VISUAL_STIMULUS, PsyStimulus)


G_MODULE_EXPORT PsyColor *
psy_visual_stimulus_get_color(PsyVisualStimulus *self);
G_MODULE_EXPORT void
psy_visual_stimulus_set_color(PsyVisualStimulus *self, PsyColor *color);

// 实现部分关键代码

G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE(PsyVisualStimulus,
                                    psy_visual_stimulus,
                                    PSY_TYPE_STIMULUS)


static void
psy_visual_stimulus_set_property(GObject      *object,
                                 guint         property_id,
                                 const GValue *value,
                                 GParamSpec   *pspec)
{
    PsyVisualStimulus *self = PSY_VISUAL_STIMULUS(object);

    switch ((VisualStimulusProperty) property_id) {
    // 省略其他属性处理
    case PROP_COLOR:
        psy_visual_stimulus_set_color(self, g_value_get_object(value));
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
    }
}

static void
psy_visual_stimulus_class_init(PsyVisualStimulusClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS(klass);
    object_class->get_property = psy_visual_stimulus_get_property;
    object_class->set_property = psy_visual_stimulus_set_property;
    object_class->dispose      = psy_visual_stimulus_dispose;

    PsyStimulusClass *stimulus_class = PSY_STIMULUS_CLASS(klass);
    stimulus_class->play             = visual_stimulus_play;
    stimulus_class->set_duration     = visual_stimulus_set_duration;

    klass->update = visual_stimulus_update;
    
    // 省略其他属性定义

    /**
     * PsyVisualStimulus:color
     *
     * 用于填充对象的`PsyColor`颜色
     */
    visual_stimulus_properties[PROP_COLOR]
        = g_param_spec_object("color",
                              "Color",
                              "刺激物的填充颜色",
                              PSY_TYPE_COLOR,
                              G_PARAM_READWRITE);

    g_object_class_install_properties(
        object_class, NUM_PROPERTIES, visual_stimulus_properties);
}

/**
 * psy_visual_stimulus_get_color:
 * @self: `PsyVisualStimulus`实例
 *
 * 获取刺激物的颜色
 *
 * Returns:(transfer none): 刺激物的填充`PsyColor`对象
 */
PsyColor *
psy_visual_stimulus_get_color(PsyVisualStimulus *self)
{
    PsyVisualStimulusPrivate *priv
        = psy_visual_stimulus_get_instance_private(self);
    g_return_val_if_fail(PSY_IS_VISUAL_STIMULUS(self), NULL);

    return priv->color;
}

/**
 * psy_visual_stimulus_set_color:
 * @self: `PsyVisualStimulus`实例
 * @color:(transfer none): 用于填充刺激物形状的`PsyColor`实例
 *
 * 设置刺激物的填充颜色
 */
void
psy_visual_stimulus_set_color(PsyVisualStimulus *self, PsyColor *color)
{
    PsyVisualStimulusPrivate *priv
        = psy_visual_stimulus_get_instance_private(self);

    g_return_if_fail(PSY_IS_VISUAL_STIMULUS(self) && PSY_IS_COLOR(color));

    g_clear_object(&priv->color);
    // psy_color_dup创建深拷贝,调用g_object_new而非增加引用计数,这可能存在问题
    // PsyColor或许应该改为boxed类型,毕竟结构很简单,但这是另一个问题
    priv->color = psy_color_dup(color);
}

问题分析与修复方案

警告来自pygobject的pygi-object.c第111行,核心原因是属性的所有权注解与实际逻辑不匹配:

1. 根本原因

  • 当前g_param_spec_object创建的属性默认假设传入的对象是借用引用(borrowed reference),但psy_visual_stimulus_set_color并未持有传入对象的引用,而是创建了深拷贝。
  • 当Python直接传递临时对象时,没有Python变量持有该对象的引用,GObject Introspection认为对象会被提前销毁,因此触发警告。

2. 修复方案

方案一:修正所有权注解与实现

  • 将psy_visual_stimulus_set_color的@color注解从(transfer none)改为(transfer full):即使内部做拷贝,也明确告诉绑定层,传入的对象所有权可以被接管,Python侧无需再持有引用。
  • 或者,若要保留(transfer none),需在set_property方法中临时持有传入对象的引用,避免Python侧提前回收:
    case PROP_COLOR: {
        PsyColor *color = g_value_get_object(value);
        g_object_ref(color);
        psy_visual_stimulus_set_color(self, color);
        g_object_unref(color);
        break;
    }
    

方案二:将PsyColor改为boxed类型(更优)

既然PsyColor是浅结构,改为boxed类型(类似GdkRGBA)可以从根本上避免引用计数问题:

  1. 使用G_DEFINE_BOXED_TYPE定义PsyColor为boxed类型,实现psy_color_copy和psy_color_free方法。
  2. 将属性定义从g_param_spec_object改为g_param_spec_boxed。
  3. 更新get/set方法的注解与实现,适配boxed类型的拷贝逻辑。

临时规避方案

若暂时不想修改C代码,可在Python中避免直接传递临时对象,先将对象赋值给变量再设置属性,或使用setter方法。但这只是规避问题,并非根本解决。


内容的提问来源于stack exchange,提问作者hetepeperfan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 04:45:04