相当于 Python 中的 C 联合?

Equivalent of a C union in Python?

提问人:shjnlee 提问时间:7/29/2017 最后编辑:Adrian Moleshjnlee 更新时间:9/3/2023 访问量:7293

问:

假设我在 C 语言中有以下代码:

union u_type
{
    uint32_t data;
    uint8_t  chunk[4];
} 32bitsdata;

32bitsdata.chunk[0] = some number;
32bitsdata.chunk[1] = some number;
32bitsdata.chunk[2] = some number;
32bitsdata.chunk[3] = some number;

printf("Data in 32 bits: %d\n", 32bitsdata.data);

我怎么能在ython中做类似的事情?

我正在尝试读取一个二进制文件(逐字节) - 已经让它工作,并将每 3 个字节组合成一个 int。听说结构可以解决问题,但我不确定如何。

Python 联合

评论

1赞 Qwerp-Derp 7/29/2017
你可以在四个变量中存储四个数字,Python 不需要类型......
1赞 shjnlee 7/29/2017
这不是我要问的重点。Union 以这种方式很方便,因为它可以将 8 位中的所有 4 位转换为一个 32 位。我正在做一个需要将数据解析为字节的项目,所以这就是为什么我在 python 中需要这个类似的函数。
1赞 Qwerp-Derp 7/29/2017
但是因为没有类型,所以在 Python 中不需要联合。我的意思是,你可以为此创建自己的类,但我看不出有什么理由。
1赞 Kevin J. Chase 7/29/2017
在 Python 中没有 a 的用处。在 Python 中,数据有类型,但变量没有。这意味着应该解决的变量类型/数据类型不匹配根本不会发生。unionunion
1赞 juanpa.arrivillaga 7/29/2017
@shjnlee它是标准库的一部分。如果你有 C 背景,它应该很容易掌握。查看文档。你可能想看看struct.iter_unpack

答:

2赞 juanpa.arrivillaga 7/29/2017 #1

这是你会做的。首先,让我们创建我们需要的原始字节,我将作弊并使用:numpy

>>> import numpy as np
>>> arr = np.array((8,4,2,4,8), dtype=np.uint32)
>>> arr
array([8, 4, 2, 4, 8], dtype=uint32)
>>> raw_bytes = arr.tobytes()
>>> raw_bytes
b'\x08\x00\x00\x00\x04\x00\x00\x00\x02\x00\x00\x00\x04\x00\x00\x00\x08\x00\x00\x00'

这些可以很容易地从文件中读取。现在,使用该模块是微不足道的。我们使用 unsigned int 格式字符:struct'I'

>>> import struct
>>> list(struct.iter_unpack('I', raw_bytes))
[(8,), (4,), (2,), (4,), (8,)]

请注意,每次迭代时,我们都会返回一个元组,因为我们的结构体有一个成员,它是一个单例元组列表。但这对于进入一个扁平的 python 列表来说是微不足道的:

>>> [t[0] for t in struct.iter_unpack('I', raw_bytes)]
[8, 4, 2, 4, 8]

另一种选择是将它们读入:array.array

>>> import array
>>> my_array = array.array('I', raw_bytes)
>>> my_array
array('I', [8, 4, 2, 4, 8])
13赞 Nick Tone 7/29/2017 #2

ctypes呢?

from ctypes import (
        Union, Array, 
        c_uint8, c_uint32, 
        cdll, CDLL
) 

class uint8_array(Array):
    _type_ = c_uint8
    _length_ = 4

class u_type(Union):
    _fields_ = ("data", c_uint32), ("chunk", uint8_array)

# load printf function from Dynamic Linked Libary libc.so.6 (I'm using linux)
libc = CDLL(cdll.LoadLibrary('libc.so.6')._name)
printf = libc.printf

if __name__ == "__main__":
    # initialize union
    _32bitsdata = u_type()
    # set values to chunk
    _32bitsdata.chunk[:] = (1, 2, 3, 4)
    # and print it
    printf(b"Data in 32 bits: %d\n", _32bitsdata.data)

评论

0赞 juanpa.arrivillaga 7/29/2017
很好。我认为 ctypes 中有一些东西可以进行精确的翻译。
0赞 Akira Cleber Nakandakare 1/10/2019 #3

你问的是 C 并集,但如果你的目标是将 3 个字节分组到一个 int 中,你可以改用 Python struct.unpack

import struct

chunk = bytearray()
chunk.append(0x00)   # some number
chunk.append(0xc0)   # some number
chunk.append(0xff)   # some number
chunk.append(0xee)   # some number

# Convert to a 32-bit unsigned int.
# You didn't specify the byte-order, so I'm using big-endian.
# If you want little-endian instead, replace the '>' symbol by '<'.
data = struct.unpack('>I', chunk)[0]  # unpack returns a tupple, but we only need the first value

print(hex(data))  # the terminal prints 0xc0ffee
1赞 Dave Rove 10/9/2019 #4

如果你正在做花哨的数值操作,你可能无论如何都想使用 numpy 库,所以考虑 numpy 的 ndarray 类型的“view”方法。可以通过 view-array 查看修改原始 ndarray。

>>> import numpy as np
>>> a = np.uint32([1234567890])
>>> b = a.view(np.uint8)
>>> print(a)
[1234567890]
>>> print(b)
[210   2 150  73]
>>> b[2] = 10
>>> print(*b)
210 2 10 73
>>> print(*a)
1225392850