将Python2代码移植到Python3时处理CType和ASCII字符串

将Python2代码移植到Python3时处理CType和ASCII字符串,python,python-3.x,ctypes,porting,visa,Python,Python 3.x,Ctypes,Porting,Visa,昨晚我受够了,开始将PyVISA移植到(progress here:) 只要我将设备地址(实际上是任何字符串)作为字符串而不是默认的unicode字符串(例如,传递,我就可以实现所有功能 HP=vida.instrument(b“GPIB::16”)工作正常,而 HP=vida.instrument(“GPIB::16”)没有,这会引发一个ValueError 理想情况下,最终用户不必关心字符串编码。 关于我应该如何处理这个问题,有什么建议吗?也许是ctypes类型定义中的一些内容 目前,相关的

昨晚我受够了,开始将PyVISA移植到(progress here:)

只要我将设备地址(实际上是任何字符串)作为字符串而不是默认的unicode字符串(例如,
传递,我就可以实现所有功能 HP=vida.instrument(b“GPIB::16”)工作正常,而 HP=vida.instrument(“GPIB::16”)没有,这会引发一个ValueError

理想情况下,最终用户不必关心字符串编码。 关于我应该如何处理这个问题,有什么建议吗?也许是ctypes类型定义中的一些内容

目前,相关的ctypes类型定义为:

ViString = _ctypes.c_char_p

ctypes
,就像Python 3中的大多数东西一样,故意不在unicode和字节之间自动转换。这是因为在大多数用例中,这只会要求使用人们切换到Python 3以避免的相同类型的mojibake或
UnicodeCodeerror
灾难

然而,当你知道你只处理纯ASCII时,那就另当别论了。你必须明确,但你可以在包装器中考虑到这种明确性


如中所述,除了标准的
ctypes
类型外,您还可以传递任何具有
from_param
classmethod的类,该类通常返回具有
\u As_参数属性的某个类型(通常是相同类型)的实例,但也可以只返回本机
ctypes
-类型值

class Asciifier(object):
    @classmethod
    def from_param(cls, value):
        if isinstance(value, bytes):
            return value
        else:
            return value.encode('ascii')
这可能不是您想要的确切规则,例如,它将在
bytearray
上失败(就像
c\u char\p
将失败一样)即使可以悄悄地将其转换为
字节
…但您也不会希望将
int
隐式转换为
字节
。任何您决定的规则都应该易于编码


下面是一个示例(在OS X上;显然,您必须更改针对linux、Windows等的
libc
加载方式,但您大概知道如何执行此操作):

libc=CDLL('libSystem.dylib') >>>libc.atoi.argtypes=[ascifier] >>>libc.atoi.restype=c_int >>>libc.atoi(b'123') 123 >>>libc.atoi('123') 123 >>>libc.atoi('123') # Unicode全宽数字 ArgumentError:参数1::“ascii”编解码器无法对位置0中的字符“\uff10”进行编码:序号不在范围内(128) >>>libc.atoi(123) ArgumentError:参数1::“int”对象没有属性“encode”
显然,如果这些异常对您的用例不够清楚,您可以捕获异常并提出不同的异常

类似地,您可以编写一个
utf8生成器
,或者一个
编码器(encoding,errors=None)
类工厂,或者为某个特定库编写任何您需要的东西,并以相同的方式将其粘贴在
argtypes


如果还希望自动解码返回类型,请参阅和



最后一件事:当您确定数据应该是UTF-8,但希望处理与Python2.x不同的情况时(通过保留它们的原样),您甚至可以在3.x中这样做。使用前面提到的
utf8发生器
作为argtype,使用解码器errcheck,然后使用。请参阅完整的示例。

@eryksun:我通常会显示
LoadLibrary
调用示例,因为这是文档中第一个非Windows示例所做的,我不想解释不相关的内容。但是现在我想起来,这有点傻,特别是因为下一行显示了一个更简单的示例。谢谢!我确实喜欢这种方法,但我希望找到一种不需要每个函数代码的解决方案。这比插入
value=value.encode('ascii')更优雅
在每一个函数定义中,但我仍然想知道我是否可以通过修改ctypes定义本身来做得更好?而不是
ViString=\ctypes.c\u char\p
类似于
ViString=\ucTypes.my\u type
的东西,我的类型继承自c\u char\p,但首先编码为ascii?@MatthewLawson:我不知道你在说什么问。什么是
ViString
?它似乎只是
c\u char\p
类型的另一个名称,所以…您如何使用它?更重要的是:您应该为通过ctypes使用的每个c函数设置
argtypes
(否则,当arg不太多时,事情往往会发生,并且它们的大小都与int完全相同,而您很幸运……这通常不够好)那么,放置
ascifier
或任何东西比
ctypes.c\u char\u p
更难吗?@MatthewLawson:实际上,看看,你使用
ViString
的目的是设置
argtypes
(通过
\u set\u参数\u types
包装方法)在您的ctypes函数中,以及在
get\u attribute
/
set\u attribute
函数中(实际上不使用该值,只需检查它是否是您存储的值),因此…您不能只
ViString=ascifier
而不做任何更改吗?(你也可能在其他地方使用它;我没有下载和确认你的代码或任何东西…)这个库(VISA库)需要它自己的所有自定义类型(bleh),所以我们需要定义这些自定义Vi类型来传递给库。在这种情况下,是的,viString只是定义为c_char_p.u set_argument_types()当前在每个函数上用于设置每个函数的预期参数类型,例如:
self.\u set\u argument\u types(“viFindRsrc”、[ViSession、ViString、ViPFindList、ViPUInt32、ViAChar])
,其中,例如,ViSession是(在其他代码中)只是定义为ViString…你的解决方案可能是最好的方式,我只是还在寻找一种更懒惰的方式。
>>> libc = CDLL('libSystem.dylib')
>>> libc.atoi.argtypes = [Asciifier]
>>> libc.atoi.restype = c_int
>>> libc.atoi(b'123')
123
>>> libc.atoi('123')
123
>>> libc.atoi('123') # Unicode fullwidth digits
ArgumentError: argument 1: <class 'UnicodeEncodeError'>: 'ascii' codec can't encode character '\uff10' in position 0: ordinal not in range(128)
>>> libc.atoi(123)
ArgumentError: argument 1: <class 'AttributeError'>: 'int' object has no attribute 'encode'