Python 由 class 键入的字典有 KeyErrors
Python dict keyed by class has KeyErrors
我正在编写一个程序来使用 ctypes 处理串行流量。同时,我正在连接一个数据库库,它使用不同但相似的 classes 将字段应用于数据缓冲区。
我想编写一个函数,它可以采用任意 ctypes 结构,遍历其字段,并调用数据库库。为此,我制作了一张 {ctypes class : database class} 的地图,并且出现了奇怪的 KeyErrors。然而,事实证明数据库库与它无关,你可以看到与 {ctypes class : string} 的字典相同的行为,如下面的最小示例:
from ctypes import *
db_map = {
c_char : "DByte",
c_byte : "DByte",
c_ubyte : "DByte",
c_ushort : "DUShort",
c_uint16 : "DUShort",
}
class command_buff(BigEndianStructure):
_pack_ = 1
_fields_ = [("frame_header", c_char ),
("command_id", c_uint8 ),
("param_value", c_uint8 ),
("crc16", c_uint16 ),
("frame_footer", c_char )]
def display(buff, database_name):
"""Applies my structure to the Dbuffer named database_name."""
global db_map
for key in db_map:
print(f"{key} {id(key)}")
print()
for field_name, c_typ, *rest in buff._fields_:
stol_typ = db_map.get(c_typ, None)
if stol_typ is None:
print(f" ERROR Can't find type {c_typ} for name {field_name}")
print(f" ERROR ({field_name}, {id(c_typ)}, {rest})")
else:
print(f"{database_name}.{field_name}")
cb = command_buff
display(cb, "Foo")
产生:
<class 'ctypes.c_char'> 2337600989576
<class 'ctypes.c_byte'> 2337600987688
<class 'ctypes.c_ubyte'> 2337600959368
<class 'ctypes.c_ushort'> 2337600969752
Foo.frame_header
Foo.command_id
Foo.param_value
ERROR Can't find type <class 'ctypes.c_ushort'> for name crc16
ERROR (crc16, 2337600963144, [])
Foo.frame_footer
如您所见,字典中的 class 'ctypes.c_ushort'
与 _fields_
成员中的 class 'ctypes.c_ushort'
具有不同的 ID,这大概就是它认为自己不在的原因字典。但我不明白怎么会这样,考虑到它们都来自完全相同的导入语句。
我看过诸如 this one 之类的问题,但大多数问题似乎都涉及具有不同 ID 的 class 的多个 实例 。在这里,似乎 class 本身 有多个 ID,即使在这么短的程序上也是如此。
为什么会发生这种情况的幕后解释是什么?
通过 class 键入字典的正确方法是什么,或者(如果这样做很愚蠢)实现映射 class -> class 的目标的正确方法是什么?
这是 ctypes 的 BigEndianStructure 工作原理的内部怪癖。
深入研究 ctypes/_endian.py
,我发现 ctypes 类型具有自身显式字节序版本的内部类型(__ctype_be__
和 __ctype_le__
)。
用于字节序结构类型的 metaclass 在其他字节序机器上交换 _fields_
中类型的字节序。
如果您有 BigEndianStructure,您的映射也需要使用那些 BigEndian (_be
) 类型:
db_map = {
c_char.__ctype_be__: "DByte",
c_byte.__ctype_be__: "DByte",
c_ubyte.__ctype_be__: "DByte",
c_ushort.__ctype_be__: "DUShort",
c_uint16.__ctype_be__: "DUShort",
}
下划线较少的另一种选择可能是仅使用类型的 __name__
并使用字符串类型的字典:
db_map = {
"c_char": "DByte",
"c_byte": "DByte",
"c_ubyte": "DByte",
"c_ushort_be": "DUShort",
"c_ushort_le": "DUShort",
"c_uint16": "DUShort",
}
# ...
db_map.get(c_typ.__name__)
我正在编写一个程序来使用 ctypes 处理串行流量。同时,我正在连接一个数据库库,它使用不同但相似的 classes 将字段应用于数据缓冲区。
我想编写一个函数,它可以采用任意 ctypes 结构,遍历其字段,并调用数据库库。为此,我制作了一张 {ctypes class : database class} 的地图,并且出现了奇怪的 KeyErrors。然而,事实证明数据库库与它无关,你可以看到与 {ctypes class : string} 的字典相同的行为,如下面的最小示例:
from ctypes import *
db_map = {
c_char : "DByte",
c_byte : "DByte",
c_ubyte : "DByte",
c_ushort : "DUShort",
c_uint16 : "DUShort",
}
class command_buff(BigEndianStructure):
_pack_ = 1
_fields_ = [("frame_header", c_char ),
("command_id", c_uint8 ),
("param_value", c_uint8 ),
("crc16", c_uint16 ),
("frame_footer", c_char )]
def display(buff, database_name):
"""Applies my structure to the Dbuffer named database_name."""
global db_map
for key in db_map:
print(f"{key} {id(key)}")
print()
for field_name, c_typ, *rest in buff._fields_:
stol_typ = db_map.get(c_typ, None)
if stol_typ is None:
print(f" ERROR Can't find type {c_typ} for name {field_name}")
print(f" ERROR ({field_name}, {id(c_typ)}, {rest})")
else:
print(f"{database_name}.{field_name}")
cb = command_buff
display(cb, "Foo")
产生:
<class 'ctypes.c_char'> 2337600989576
<class 'ctypes.c_byte'> 2337600987688
<class 'ctypes.c_ubyte'> 2337600959368
<class 'ctypes.c_ushort'> 2337600969752
Foo.frame_header
Foo.command_id
Foo.param_value
ERROR Can't find type <class 'ctypes.c_ushort'> for name crc16
ERROR (crc16, 2337600963144, [])
Foo.frame_footer
如您所见,字典中的 class 'ctypes.c_ushort'
与 _fields_
成员中的 class 'ctypes.c_ushort'
具有不同的 ID,这大概就是它认为自己不在的原因字典。但我不明白怎么会这样,考虑到它们都来自完全相同的导入语句。
我看过诸如 this one 之类的问题,但大多数问题似乎都涉及具有不同 ID 的 class 的多个 实例 。在这里,似乎 class 本身 有多个 ID,即使在这么短的程序上也是如此。
为什么会发生这种情况的幕后解释是什么?
通过 class 键入字典的正确方法是什么,或者(如果这样做很愚蠢)实现映射 class -> class 的目标的正确方法是什么?
这是 ctypes 的 BigEndianStructure 工作原理的内部怪癖。
深入研究 ctypes/_endian.py
,我发现 ctypes 类型具有自身显式字节序版本的内部类型(__ctype_be__
和 __ctype_le__
)。
用于字节序结构类型的 metaclass 在其他字节序机器上交换 _fields_
中类型的字节序。
如果您有 BigEndianStructure,您的映射也需要使用那些 BigEndian (_be
) 类型:
db_map = {
c_char.__ctype_be__: "DByte",
c_byte.__ctype_be__: "DByte",
c_ubyte.__ctype_be__: "DByte",
c_ushort.__ctype_be__: "DUShort",
c_uint16.__ctype_be__: "DUShort",
}
下划线较少的另一种选择可能是仅使用类型的 __name__
并使用字符串类型的字典:
db_map = {
"c_char": "DByte",
"c_byte": "DByte",
"c_ubyte": "DByte",
"c_ushort_be": "DUShort",
"c_ushort_le": "DUShort",
"c_uint16": "DUShort",
}
# ...
db_map.get(c_typ.__name__)