子类化ctypes指针 - 这段代码是如何工作的,以及如何覆盖它?

时间:2012-12-13 19:09:03

标签: python ctypes

我正在使用pylibtiff库(托管here),我正在尝试扩展class TIFF(ctypes.c_void_p)。我在使用以下方法时遇到问题:

# lib has been loaded with the path to libtiff.so, or equivalent
libtiff = ctypes.cdll.LoadLibrary(lib)

class TIFF(ctypes.c_void_p):
    @classmethod
    def open(cls, filename, mode='r'):
        """ Open tiff file as TIFF.
        """
        tiff = libtiff.TIFFOpen(filename, mode)
        if tiff.value is None:
            raise TypeError ('Failed to open file '+`filename`)
        return tiff

(全班here

现在,这里的预期使用模式是我们打开一个文件并收到一个class TIFF实例,如下所示:

import libtiff
t = libtiff.TIFF.open('myfile.tiff')

# e.g. grab a numpy array of the data:
arr = t.read_image()

这是什么魔法?来自libtiff typed as a TIFF*的C变量如何返回到pythonland并自动成为class TIFF实例?大概这与ctypes.c_void_p

的子类化有关

我问,因为我试图覆盖class TIFF上的方法:

import libtiff
class TIFFdifferent(libtiff.TIFF):
    def read_image(self, verbose=False, different=False):
        if not different:
            return libtiff.TIFF.read_image(self, verbose)

        # my different code ...

但是,当我尝试创建class TIFFdifferent个实例时,我得到class TIFF代替:

In [3]: t = libtiffdifferent.TIFFdifferent.open('file.tiff')

In [4]: arr = t.read_image(different=True)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-8bdc24ec874c> in <module>()
----> 1 arr = t.read_image(different=True)

TypeError: read_image() got an unexpected keyword argument 'different'

In [5]: t.read_image?
Type:       instancemethod
String Form:<bound method TIFF.read_image of <TIFF object at 0x10b67df80>>
File:       /Library/Python/2.7/site-packages/libtiff/libtiff_ctypes.py
Definition: t.read_image(self, verbose=False)
Docstring:
Read image from TIFF and return it as an array.


In [6]: t
Out[6]: <TIFF object at 0x10b67df80>

所以,我需要做的是覆盖open - 我不知道怎么做而不理解从C TIFF*到python class TIFF实例的魔术转换,没有构造函数,强制转换或任何其他明确的。

1 个答案:

答案 0 :(得分:2)

魔法在libtiff_ctypes.py的第1041行:

libtiff.TIFFOpen.restype = TIFF

正如ctypes文档中的15.17.1.8 Return types所述,您不必使用ctypes类型作为结果类型;你可以指定任何Python可调用 - 通常是一个函数,它将验证实际的C返回值以引发异常,或者一个类,它将真正的C返回值作为其构造函数参数。

您可能会注意到TIFF类没有定义__init__方法。这是来自c_void_p的继承的地方:您可以从指针构造c_void_p;因此,如果你有一个c_void_p的子类,并且它不需要进行任何其他初始化,它可以依靠基类构造来处理它。

  

所以,我需要做的是覆盖open - 我不知道怎么做而不理解从C TIFF*到python类TIFF实例的魔术转换,没有构造函数,强制转换或任何其他显式。

好吧,你显然可以在你的覆盖中做一个明确的构造调用,即使原始版本没有这样做:

def myopen(*args):
    tiffpointer = libtiff.cfunc(*args)
    return TIFF(tiffpointer)

但是如果你不能只定义libtiff.cfunc.restype,你只想这样做(例如,因为pylibtiff中的其他一些代码依赖于它返回不同的东西)。

无论如何,我不确定这是你真正想做的事情。你要做的是写下这样的东西:

class TIFFDifferent(TIFF):
    …
    @classmethod
    def open(cls, filename, mode='r'):
        tiff = libtiff.TIFFOpen(filename, mode)
        if tiff.value is None:
            raise TypeError ('Failed to open file '+`filename`)
        return cls(tiff)

这是有效的,你无需了解restype背后的魔力。除非你担心构造一个短暂的临时TIFF对象的成本(正如你所见,没有成员,没有构造函数代码超出c_void_p的对象),isn'这很简单吗?

还有一种可能性,我通常不建议,也可能在这里不合适,但可能是:如果pylibtiff的设计方式是通过子类覆盖其行为很难做到,你可以(a)分叉代码并使用你自己的版本,或(b)在运行时monkeypatch它。正如您自己的评论中所建议的那样:

class TIFFDifferent(TIFF):
    …
    @classmethod
    def open(cls, filename, mode='r'):
        stash = libtiff.TIFFOpen.restype
        libtiff.TIFFOpen.restype = cls
        tiff = libtiff.TIFFOpen(filename, mode)
        libtiff.TIFFOpen.restype = stash
        if tiff.value is None:
            raise TypeError ('Failed to open file '+`filename`)
        return tiff

如果您的构造函数可能抛出异常,您希望将隐藏包裹在contextmanager(或使用try: / finally:)中。否则你将离开restype补丁。