【问题标题】:Initialize PyObject without calling __init__在不调用 __init__ 的情况下初始化 PyObject
【发布时间】:2018-07-09 01:29:15
【问题描述】:

我有一个包装类,如下例所示,用 Python 编写。

class Foo:
    def __init__(self, bar=None):
        if bar:
            self._bar = bar
        else:
            self._bar = CreateBarObject("value") 

我通过 Python C API 创建一个实例

// c++ pseudo code for convenience

auto obj = PyObject_CallObject(Foo) 
auto bar = CreateBarObject("another_value");
PyObject_SetAttrString(obj, "_bar", bar)

从代码中可以看出,Foo.__init__ 将在创建实例时调用,该实例创建一个新的bar 对象。但我想绕过这个“重”操作。那么创建 Foo 实例的任何安全方法,以便我可以通过 Python C API 设置 self._bar 吗?有什么想法吗?

【问题讨论】:

  • 为什么要通过C API设置_bar?为什么不在初始调用中将bar 对象传递给Foo
  • Foo() 没有参数需要创建self._bar 以使实例工作。但是这个调用是一个非常昂贵的操作,所以我想绕过这个成本加上绕过不必要的字节码指令,如果可能的话,它需要一个非常轻量级的初始化
  • "Foo() 没有参数需要创建 self._bar 以使实例工作" - 是的,所以 为什么你不传递参数
  • 那么data是什么?
  • 因为调用__init__if bar至少对这个类有显着的性能影响,所以我尝试对其进行优化。顺便说一句,你是对的,数据应该是bar。谢谢指出

标签: python c cpython python-internals


【解决方案1】:

你应该可以直接调用Foo中的the tp_new(相当于在Python层调用Foo.__new__)。这将执行分配和 C 级“强制初始化”工作,而没有 tp_init/__init__ 的“可选初始化”工作。这与pickle 用于创建类型实例而不初始化它们的策略相同(因此它可以通过__setstate__ 填充它们或直接填充__dict__,视情况而定)。

假设 Foo 是 C 级 PyTypeObject*,这样的事情应该可以解决问题:

auto emptytup = PyTuple_New(0);  /* AFAICT, the args tuple is mandatory, but kwargs is optional */
/* Error check for PyTuple_New failure */
auto obj = Foo->tp_new(Foo, emptytup, NULL);
Py_DECREF(emptytup);
/* Error check for new fail */
auto bar = CreateBarObject("another_value");
PyObject_SetAttrString(obj, "_bar", bar)

【讨论】:

    【解决方案2】:

    self.bar 设为在第一次需要时创建实例的属性。

    class Foo:
    
        def __init__(self, bar=None):
            self._bar = bar
    
        @property
        def bar(self):
            if self._bar is None:
                self._bar = CreateBarObject(...)
            return self._bar
    

    实例功能齐全。实例的bar 属性将在第一次需要时创建Bar 对象。或者您可以在此之前从 C API 设置 _bar,然后使用它。或者您可以在实例化类时传入现有的Bar 对象。您的选择。

    【讨论】:

    • 因为调用__init__if bar 至少对这个类有显着的性能影响,所以我想在这种情况下摆脱Python 字节码执行。但是对于一般的方法,这是一个非常好的主意,从来没有想过
    • 你可以直接取消__init__,然后添加一个类属性_bar = None。这将消除 Foo 初始化处的所有字节码。
    • Foo() 仍然需要有一个 _bar 成员和 CreateCreateObject("value")。但我想我找到了 ShadowRanger 发布的解决方案。非常感谢您的意见!
    • 您现在已将性能问题转移到 each bar 访问,而不仅仅是构造函数。在这种情况下,这至少应该是一个惰性/具体化非数据描述符。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-09
    • 2022-12-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多