无法覆盖Cython扩展的类的__init__

时间:2013-08-15 19:20:53

标签: python inheritance cython

我正在尝试子类化pysam's Tabixfile类并在实例化时添加其他属性。

class MyTabixfile(pysam.Tabixfile):

    def __init__(self, filename, mode='r', *args, **kwargs):
        super().__init__(filename, mode=mode, *args, **kwargs)
        self.x = 'foo'

当我尝试实例化我的MyTabixfile子类时,我得到一个TypeError: object.__init__() takes no parameters

>>> mt = MyTabixfile('actn2-oligos-forward.tsv.gz')
Traceback (most recent call last):
  File "<ipython-input-11-553015ac7d43>", line 1, in <module>
    mt = MyTabixfile('actn2-oligos-forward.tsv.gz')
  File "mytabix.py", line 4, in __init__
    super().__init__(filename, mode=mode, *args, **kwargs)
TypeError: object.__init__() takes no parameters

我还尝试明确调用Tabixfile构造函数:

class MyTabixfile(pysam.Tabixfile):

    def __init__(self, filename, mode='r', *args, **kwargs):
        pysam.Tabixfile.__init__(self, filename, mode=mode, *args, **kwargs)
        self.x = 'foo'

但这仍然会引发TypeError: object.__init__() takes no parameters

这个类实际上是在Cython中实现的;构造函数代码如下:

cdef class Tabixfile:
    '''*(filename, mode='r')*

    opens a :term:`tabix file` for reading. A missing
    index (*filename* + ".tbi") will raise an exception.
    '''
    def __cinit__(self, filename, mode = 'r', *args, **kwargs ):
        self.tabixfile = NULL
        self._open( filename, mode, *args, **kwargs )

我通读Cython documentation on __cinit__ and __init__说了

  

传递给构造函数的任何参数都将传递给   __cinit__()方法和__init__()方法。 如果你预料到的话   在Python中继承您的扩展类型,您可能会发现它很有用   提供__cinit__()方法***参数以便它可以   接受并忽略额外的参数。否则,任何Python子类   具有__init__()具有不同签名的必须具有   覆盖__new__() 1以及__init__(),作者的作者   一个Python类不希望这样做。

pysam开发人员确实谨慎地将*args**kwargs添加到Tabixfile.__cinit__方法,我的子类__init__与签名匹配__cinit__的{​​{1}}所以我不明白为什么我无法覆盖Tabixfile的初始化。

我正在使用Python 3.3.1,Cython v.0.19.1和pysam v.0.7.5进行开发。

2 个答案:

答案 0 :(得分:17)

这里的文档有点令人困惑,因为它假定您熟悉使用__new____init__

__cinit__方法大致相当于Python中的__new__方法。*

__new__一样,__cinit__ <{1}}在Python甚至到达你的子类的super().__init__方法之前调用它。 __init__需要处理子类__cinit__方法签名的原因与__init__完全相同。

如果您的子类明确调用__new__,那么再次在超类中查找super().__init__方法,如__init____new__不是{{1} }}。因此,除非您定义了__cinit__,否则它将传递到__init__


您可以使用以下代码查看序列。

cinit.pyx:

__init__

init.py:

object

运行时,您会看到类似的内容:

cdef class Foo:
    def __cinit__(self, a, b, *args, **kw):
        print('Foo.cinit', a, b, args, kw)
    def __init__(self, *args, **kw):
        print('Foo.init', args, kw)

所以,这里正确的解决方案取决于你想要做什么,但它是其中之一:

  1. 将一个import pyximport; pyximport.install() import cinit class Bar(cinit.Foo): def __new__(cls, *args, **kw): print('Bar.new', args, kw) return super().__new__(cls, *args, **kw) def __init__(self, a, b, c, d): print('Bar.init', a, b, c, d) super().__init__(a, b, c, d) b = Bar(1, 2, 3, 4) 方法添加到Cython基类。
  2. 完全删除Bar.new (1, 2, 3, 4) {} Foo.cinit 1 2 (3, 4) {} Bar.init 1 2 3 4 Foo.init (1, 2, 3, 4) {} 来电。
  3. __init__更改为不传递任何参数。
  4. 在Python子类中添加适当的super().__init__方法。
  5. 我怀疑在这种情况下它是你想要的#2。


    *值得注意的是,super().__init__肯定与<{1}}不是相同。您可以获得部分构造的__new__对象(您可以信任__cinit__和C属性但不信任Python属性或方法),__new__,而不是获取cls参数。 MRO中所有类的方法已经在任何self之前被调用;您的基地的__class__会自动调用而不是手动调用;除了被请求的对象之外,你不会返回另一个对象;只是它在__new__之前被调用,并且期望采用传递参数,与__cinit__的方式相同。

答案 1 :(得分:1)

我会评论而不是发布答案,但我还没有足够的StackOverflow foo。

@ abarnert的帖子非常好,非常有帮助。我只是在这里添加一些pysam细节,因为我刚刚以非常类似的方式对pysam.AlignmentFile进行了子类化。

选项#4是最干净/最简单的选择,它只意味着我自己的子类__new__中的更改来过滤掉未知的参数:

def __new__(cls, file_path, mode, label=None, identifier=None, *args, **kwargs):
    # Suck up label and identifier unknown to pysam.AlignmentFile.__cinit__
    return super().__new__(cls, file_path, mode, *args, **kwargs)

还应该注意,pysam文件类似乎没有明确的__init__方法,所以你还需要省略param pass through,因为它直接转向对象.__ init__,它不接受参数:

def __init__(self, label=None, identifier=None, *args, **kwargs):
    # Handle subclass params/attrs here
    # pysam.AlignmentFile doesn't have an __init__ so passes straight through to
    # object which doesn't take params. __cinit__ via new takes care of params
    super(pysam.AlignmentFile, self).__init__()