扩展 SWIG 内置类

发布于 2024-11-16 22:36:03 字数 427 浏览 2 评论 0原文

SWIG 的 -builtin 选项的优点是速度更快,并且可以避免多重继承的错误。
挫折是我无法在生成的类或任何子类上设置任何属性:
-我可以通过子类化 python 内置类型(如列表)而轻松地扩展它:

class Thing(list):
    pass

Thing.myattr = 'anything' # No problem

-但是,在 SWIG 内置类型上使用相同的方法,会发生以下情况:

class Thing(SWIGBuiltinClass):
    pass

Thing.myattr = 'anything'

AttributeError: type object 'Thing' has no attribute 'myattr'

我如何解决这个问题?

The -builtin option of SWIG has the advantage of being faster, and of being exempt of a bug with multiple inheritance.
The setback is I can't set any attribute on the generated classes or any subclass :
-I can extend a python builtin type like list, without hassle, by subclassing it :

class Thing(list):
    pass

Thing.myattr = 'anything' # No problem

-However using the same approach on a SWIG builtin type, the following happens :

class Thing(SWIGBuiltinClass):
    pass

Thing.myattr = 'anything'

AttributeError: type object 'Thing' has no attribute 'myattr'

How could I work around this problem ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

挽心 2024-11-23 22:36:03

我很偶然地找到了一个解决方案。我正在尝试元类,认为我可以设法覆盖子类中内置类型的 setattrgetattr 函数。

这样做我发现内置函数已经有一个元类(SwigPyObjectType),所以我的元类必须继承它。

就是这样。仅此一点就解决了问题。如果有人能解释原因,我会很高兴:

SwigPyObjectType = type(SWIGBuiltinClass)

class Meta(SwigPyObjectType):
    pass

class Thing(SWIGBuiltinClass):
    __metaclass__ = Meta

Thing.myattr = 'anything' # Works fine this time

I found a solution quite by accident. I was experimenting with metaclasses, thinking I could manage to override the setattr and getattr functions of the builtin type in the subclass.

Doing this I discovered the builtins already have a metaclass (SwigPyObjectType), so my metaclass had to inherit it.

And that's it. This alone solved the problem. I would be glad if someone could explain why :

SwigPyObjectType = type(SWIGBuiltinClass)

class Meta(SwigPyObjectType):
    pass

class Thing(SWIGBuiltinClass):
    __metaclass__ = Meta

Thing.myattr = 'anything' # Works fine this time
︶葆Ⅱㄣ 2024-11-23 22:36:03

问题来自于 swig 如何实现“-builtin”中的类,就像内置类一样(因此得名)。

内置类不可扩展 - 尝试添加或修改“str”的成员,并且 python 不会让您修改属性字典。

我确实有一个已经使用多年的解决方案。

我不确定我是否可以推荐它,因为:

  1. 它可以说是邪恶的 - 道德上等同于在 C/C++ 中抛弃 const-ness
  2. 它不受支持,并且可能会在未来的 python 版本中崩溃
  3. 我还没有在 python3 中尝试过它
  4. 我会有点在生产代码中使用这样的“黑魔法”会让人感到不舒服 - 它可能会破坏并且肯定是晦涩难懂的 - 但至少有一家大公司在生产代码中使用它

但是..我喜欢它在解决我们想要的一些晦涩功能方面的效果调试。

最初的想法不是我的,我是从以下地方得到的:
https://gist.github.com/mahmoudimus/295200 作者:Mahmoud Abdelkader

基本思想是将 swig 创建的类型对象中的 const 字典作为非常量字典进行访问,并添加/覆盖任何所需的方法。

仅供参考,类的运行时修改技术称为 Monkeypatching,请参阅 https://en.wikipedia.org/ wiki/Monkey_patch

首先 - 这是“monkeypatch.py​​”:

''' monkeypatch.py:
I got this from https://gist.github.com/mahmoudimus/295200 by Mahmoud Abdelkader,
his comment: "found this from Armin R. on Twitter, what a beautiful gem ;)"
I made a few changes for coding style preferences
- Rudy Albachten   April 30 2015
'''

import ctypes
from types import DictProxyType, MethodType

# figure out the size of _Py_ssize_t
_Py_ssize_t = ctypes.c_int64 if hasattr(ctypes.pythonapi, 'Py_InitModule4_64') else ctypes.c_int

# python without tracing
class _PyObject(ctypes.Structure):
    pass
_PyObject._fields_ = [
    ('ob_refcnt', _Py_ssize_t),
    ('ob_type', ctypes.POINTER(_PyObject))
]

# fixup for python with tracing
if object.__basicsize__ != ctypes.sizeof(_PyObject):
    class _PyObject(ctypes.Structure):
        pass
    _PyObject._fields_ = [
        ('_ob_next', ctypes.POINTER(_PyObject)),
        ('_ob_prev', ctypes.POINTER(_PyObject)),
        ('ob_refcnt', _Py_ssize_t),
        ('ob_type', ctypes.POINTER(_PyObject))
    ]

class _DictProxy(_PyObject):
    _fields_ = [('dict', ctypes.POINTER(_PyObject))]

def reveal_dict(proxy):
    if not isinstance(proxy, DictProxyType):
        raise TypeError('dictproxy expected')
    dp = _DictProxy.from_address(id(proxy))
    ns = {}
    ctypes.pythonapi.PyDict_SetItem(ctypes.py_object(ns), ctypes.py_object(None), dp.dict)
    return ns[None]

def get_class_dict(cls): 
    d = getattr(cls, '__dict__', None)
    if d is None:
        raise TypeError('given class does not have a dictionary')
    if isinstance(d, DictProxyType):
        return reveal_dict(d)
    return d

def test():
    import random
    d = get_class_dict(str)
    d['foo'] = lambda x: ''.join(random.choice((c.upper, c.lower))() for c in x)
    print "and this is monkey patching str".foo()

if __name__ == '__main__':
    test()

这是一个使用 Monkeypatch 的人为示例:

我在模块“mystuff”中有一个类“myclass”,包裹着swig -python -builtin

我想添加一个额外的运行时方法“namelen”,它返回 myclass.getName() 返回的名称的长度

import mystuff
import monkeypatch

# add a "namelen" method to all "myclass" objects
def namelen(self):
    return len(self.getName())
d = monkeypatch.get_class_dict(mystuff.myclass)
d['namelen'] = namelen

x = mystuff.myclass("xxxxxxxx")
print "namelen:", x.namelen()

请注意,这也可以用于扩展或覆盖内置 python 类上的方法,如所示在 Monkeypatch.py​​ 的测试中:它向内置 str 类添加了一个方法“foo”,该方法返回带有随机大/小写字母的原始字符串的副本,

我可能会替换:

# add a "namelen" method to all "myclass" objects
def namelen(self):
    return len(self.getName())
d = monkeypatch.get_class_dict(mystuff.myclass)
d['namelen'] = namelen

with

# add a "namelen" method to all "myclass" objects
monkeypatch.get_class_dict(mystuff.myclass)['namelen'] = lambda self: return len(self.getName())

以避免额外的全局变量

The problem comes from how swig implemented the classes in "-builtin" to be just like builtin classes (hence the name).

builtin classes are not extensible - try to add or modify a member of "str" and python won't let you modify the attribute dictionary.

I do have a solution I've been using for several years.

I'm not sure I can recommend it because:

  1. It's arguably evil - the moral equivalent of casting away const-ness in C/C++
  2. It's unsupported and could break in future python releases
  3. I haven't tried it with python3
  4. I would be a bit uncomfortable using "black-magic" like this in production code - it could break and is certainly obscure - but at least one giant corporation IS using this in production code

But.. I love how well it works to solve some obscure features we wanted for debugging.

The original idea is not mine, I got it from:
https://gist.github.com/mahmoudimus/295200 by Mahmoud Abdelkader

The basic idea is to access the const dictionary in the swig-created type object as a non-const dictionary and add/override any desired methods.

FYI, the technique of runtime modification of classes is called monkeypatching, see https://en.wikipedia.org/wiki/Monkey_patch

First - here's "monkeypatch.py":

''' monkeypatch.py:
I got this from https://gist.github.com/mahmoudimus/295200 by Mahmoud Abdelkader,
his comment: "found this from Armin R. on Twitter, what a beautiful gem ;)"
I made a few changes for coding style preferences
- Rudy Albachten   April 30 2015
'''

import ctypes
from types import DictProxyType, MethodType

# figure out the size of _Py_ssize_t
_Py_ssize_t = ctypes.c_int64 if hasattr(ctypes.pythonapi, 'Py_InitModule4_64') else ctypes.c_int

# python without tracing
class _PyObject(ctypes.Structure):
    pass
_PyObject._fields_ = [
    ('ob_refcnt', _Py_ssize_t),
    ('ob_type', ctypes.POINTER(_PyObject))
]

# fixup for python with tracing
if object.__basicsize__ != ctypes.sizeof(_PyObject):
    class _PyObject(ctypes.Structure):
        pass
    _PyObject._fields_ = [
        ('_ob_next', ctypes.POINTER(_PyObject)),
        ('_ob_prev', ctypes.POINTER(_PyObject)),
        ('ob_refcnt', _Py_ssize_t),
        ('ob_type', ctypes.POINTER(_PyObject))
    ]

class _DictProxy(_PyObject):
    _fields_ = [('dict', ctypes.POINTER(_PyObject))]

def reveal_dict(proxy):
    if not isinstance(proxy, DictProxyType):
        raise TypeError('dictproxy expected')
    dp = _DictProxy.from_address(id(proxy))
    ns = {}
    ctypes.pythonapi.PyDict_SetItem(ctypes.py_object(ns), ctypes.py_object(None), dp.dict)
    return ns[None]

def get_class_dict(cls): 
    d = getattr(cls, '__dict__', None)
    if d is None:
        raise TypeError('given class does not have a dictionary')
    if isinstance(d, DictProxyType):
        return reveal_dict(d)
    return d

def test():
    import random
    d = get_class_dict(str)
    d['foo'] = lambda x: ''.join(random.choice((c.upper, c.lower))() for c in x)
    print "and this is monkey patching str".foo()

if __name__ == '__main__':
    test()

Here's a contrived example using monkeypatch:

I have a class "myclass" in module "mystuff" wrapped with swig -python -builtin

I want to add an extra runtime method "namelen" that returns the length of the name returned by myclass.getName()

import mystuff
import monkeypatch

# add a "namelen" method to all "myclass" objects
def namelen(self):
    return len(self.getName())
d = monkeypatch.get_class_dict(mystuff.myclass)
d['namelen'] = namelen

x = mystuff.myclass("xxxxxxxx")
print "namelen:", x.namelen()

Note that this can also be used to extend or override methods on builtin python classes, as is demonstrated in the test in monkeypatch.py: it adds a method "foo" to the builtin str class that returns a copy of the original string with random upper/lower case letters

I would probably replace:

# add a "namelen" method to all "myclass" objects
def namelen(self):
    return len(self.getName())
d = monkeypatch.get_class_dict(mystuff.myclass)
d['namelen'] = namelen

with

# add a "namelen" method to all "myclass" objects
monkeypatch.get_class_dict(mystuff.myclass)['namelen'] = lambda self: return len(self.getName())

to avoid extra global variables

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文