【问题标题】:Should type information be provided for the 'self' argument in cython extension types?是否应该为 cython 扩展类型中的“自我”参数提供类型信息?
【发布时间】:2017-10-16 14:48:58
【问题描述】:

我一直在尝试用 cython 封装 C++。我试图了解在扩展类型方法中键入 self 的含义。

docs 中,self 没有显式键入,但似乎与键入 self 相关的速度可能会加快。

但是,在我有限的实验中,显式输入 self 似乎不会提高性能。处理self的幕后是否有特殊的魔法,或者这纯粹是一种风格的事情?

编辑为清楚起见:

通过输入self,我的意思是为方法的self 参数提供类型信息。即:

cdef class foo:
    cpdef bar(self):
        # do stuff with self

cdef class foo:
    cpdef bar(foo self):
        # do stuff with self

【问题讨论】:

  • 我同意为自己的类对象键入“self”或任何其他名称应该是纯粹的约定问题...
  • 我认为没有任何区别,因为在这两种情况下,生成的 c 代码 self 在 c 部分转换为 struct xxxxxx_foo *__pyx_v_self,在 Python 包装器中转换为 PyObject *__pyx_v_self。(您可以轻松通过%%cython -a cell magic 在jupyter notebook 中检查)
  • @oz1 看起来你对一般情况是正确的。我开始走这条路的原因是显然需要在__eq__ 方法中键入self。我做了你的笔记本技巧,__eq__ 和其他神奇功能确实存在差异。
  • (struct __pyx_obj_46_cython_magic_a97a4609d42faba2d2376a5b3cd09730_foo *)__pyx_v_self 一个,(PyObject *)__pyx_v_self 另一个
  • 据我所知,特殊方法必须是def函数,因此在python包装器中self应该总是(PyObject *)__pyx_v_self。实际上,包装函数在签名中有所不同,一个是 (PyObject *) 表示未键入 self,另一个是 struct *,它可能具有较少的指针间接寻址。我觉得python部分的性能差别不大。

标签: cython


【解决方案1】:

简答:

没有必要在类方法中详细键入self。它并不比普通的 self 快多少。

长答案:

虽然生成的c代码确实存在一些差异(可以在jupyter notebook中使用magic cell%%cython -a轻松查看)。例如:

%%cython -a

# Case 1
cdef class foo1:
    def bar(self, foo1 other):
        pass
    def __eq__(self, foo1 other):
        pass

# Case 2
cdef class foo2:
    def bar(self, foo2 other):
        pass
    def __eq__(foo2 self, foo2 other):
        pass
  1. 在 Python 包装器中,self 始终转换为 PyObject *

  2. 对于普通方法(bar),包装后的 C 函数签名是相同的,self 都转换为 struct xxx_foo *

  3. 对于魔术方法(__eq__),在包装的 C 函数中,普通的self 被转换为PyObject *,但类型化的foo2 self 被转换为struct xxx_foo2 *。在后一种情况下,python 包装器将 PyObject * 转换为 struct xxx_foo2 * 并调用包装的 C 函数。情况 2 可能有更少的指针间接寻址,但两种情况下的性能应该没有太大差异。此外,案例 2 将在 python 包装器中进行更多检查。在实践中,个人资料可以说明一切。

【讨论】:

    【解决方案2】:

    正如您已经解决的那样,通常self 在生成的 c 代码中被“翻译”为正确的类型。

    我知道的唯一例外是rich comparison operators,即__eq____lt____le__等等。

    +=+ 等其他特殊方法/运算符的工作方式与所有其他“正常”方法完全相同:self 自动属于正确的类型。

    但是,富比较运算符的行为很快就会改变,因为这似乎只是新引入的功能中的一个小故障:corresponding issue


    现在,我们已经确定了 cython 做了什么,有趣的问题是为什么 cython 会这样做。

    对于来自静态类型语言的人来说,很明显,self 只能是定义此函数的类类型(确切地说是此类或派生自此类),所以我希望 @987654331 @ 属于此类类型。因此,如果 cython 的行为有所不同,那将是一个惊喜。

    然而,在鸭子打字和 mokey-patching 时代,哪些类可以动态更改可能还不是很清楚。我们来看下面的例子:

     []class A:
        def __init__(self, val):
             self.val=val
        def __str__(self):
           return "value=%s"%self.val
    
     []class B:
         def __init__(self, val):
            self.val="<"+val+">"
    
     [] a,b=A(1.0),B("div")
     [] print a
     value=3
     [] print b
     <__main__.B instance at 0x0000000003D24E08>
    

    如果我们不喜欢 print 如何处理 B 类。可以通过以下方式对 B 类进行猴子补丁:

     []B.__str__=lambda self: "value=%s"%self.val
     []print b
     value=<div>
    

    因此,如果我们喜欢 A 类处理 __str__ 方法的方式,我们可以尝试“重用”它:

     []B.__str__=lambda self: A.__str__(self)
     []print b
     TypeError: unbound method __str__() must be called 
                with A instance as first argument 
                (got B instance instead)
    

    所以这是不可能的:python 检查对A.__str__(self) 的调用是否self 确实是A 类型。

    因此,cython 直接为 self 使用正确的类型而不是 python 对象是正确的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-24
      • 2019-05-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多