7

我正在尝试对pysam 的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 文档__cinit____init__其中说

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

pysam 开发人员确实小心地将*args和添加**kwargsTabixfile.__cinit__方法中,并且我的子类__init__与签名匹配,__cinit__所以我不明白为什么我无法覆盖Tabixfile.

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

4

2 回答 2

18

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

__cinit__方法大致相当于__new__Python 中的一个方法。*

喜欢__new____cinit__不是叫的super().__init__;它在 Python 甚至到达您的子类的__init__方法之前被调用。__cinit__需要处理子类方法签名的原因__init__与完全相同的原因__new__

如果您的子类确实显式调用了 ,那么它会在超类super().__init__中查找方法——同样,a不是 a 。因此,除非您定义了,否则它将传递到.__init____new____cinit____init____init__object


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

cinit.pyx:

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)

初始化.py:

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)

运行时,您会看到如下内容:

Bar.new (1, 2, 3, 4) {}
Foo.cinit 1 2 (3, 4) {}
Bar.init 1 2 3 4
Foo.init (1, 2, 3, 4) {}

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

  1. __init__向 Cython 基类添加一个方法。
  2. 完全删除super().__init__呼叫。
  3. 更改super().__init__为不传递任何参数。
  4. __new__向 Python 子类添加适当的方法。

我怀疑在这种情况下它是你想要的#2。


* 值得注意的是,它__cinit__绝对不等同__new__. 不是获取cls参数,而是获取部分构造的self对象(您可以信任__class__C 属性,但不能信任 Python 属性或方法),__new__MRO 中所有类的方法在 any 之前已经被调用__cinit__;你的__cinit__基地被自动调用而不是手动调用;除了请求的对象之外,您不会返回其他对象;等等。只是它在 之前被调用__init__,并且期望以与原样相同的方式获取传递参数__new__

于 2013-08-15T19:51:45.660 回答
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,因为它直接传递给不接受参数的 object.__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__()
于 2017-06-02T11:00:26.840 回答