使用纯Python将`memoryview`转置

3

有没有一种纯Python的方法可以转置 memoryview


Python中的memoryview不仅仅可以表示一维字节块,还可以表示多维布局、非连续内存、复杂元素类型等等。例如,在下面的代码中:

In [1]: import numpy

In [2]: x = numpy.array([[1, 2], [3, 4]])

In [3]: y = x.T

In [4]: a = memoryview(x)

In [5]: b = memoryview(y)

ab 是两个 2x2 的多维内存视图:

In [6]: a.shape
Out[6]: (2, 2)

In [7]: b.shape
Out[7]: (2, 2)

b代表a的转置,因此a[i, j]b[j, i]别名引用相同的内存(即原始x数组的第i行第j列):

In [8]: a[0, 1] = 5

In [9]: b[1, 0]
Out[9]: 5

In [10]: x
Out[10]: 
array([[1, 5],
       [3, 4]])

NumPy数组支持简单的转置操作,但是NumPy数组并不是仅有的多维内存视图来源。例如,您可以将单维度内存视图转换为多维度内存视图:

In [11]: bytearr = bytearray([1, 2, 3, 4])

In [12]: mem = memoryview(bytearr).cast('b', (2, 2))

In [13]: mem.shape
Out[13]: (2, 2)

In [14]: mem[1, 0] = 5

In [15]: bytearr
Out[15]: bytearray(b'\x01\x02\x05\x04')

memoryview的格式足够灵活,可以表示与mem倒置的格式,就像我们之前示例中a对应的b一样。但是,在memoryview API中似乎没有易于倒置的方法。是否有一种纯Python的方式可以倒置任意多维memoryviews?


2
一个memoryview的对象是_什么_?为什么无论它是什么,用“正常”的方法转置都不起作用呢? - martineau
3
不完全正确。一个memoryview可以有形状(即可以是多维的),并且可以访问内存。这就是NumPy数组的memoryview工作原理。参考链接:https://docs.python.org/zh-cn/3/library/stdtypes.html#memoryview.shape - jakirkham
1
@pkqxdd 所有真正的多维数组,例如 numpy,都是使用一块连续分配的内存来实现的... - juanpa.arrivillaga
1
@martineau: 我们不应该关心底层对象是什么。底层对象可能根本没有转置的概念,或者如果有,可能不支持不进行复制就进行转置。Memoryview形状和步幅元数据足够灵活,可以表示转置而不进行复制,但是memoryview Python级别接口没有暴露该功能。 - user2357112
1
例如,可以通过类似于memoryview(bytearray(b'asdf')).cast('b', (2, 2))的方式,创建一个由bytearray支持的多维memoryview。虽然bytearray是一维的,但仍然可以对memoryview进行转置。 - user2357112
显示剩余2条评论
2个回答

3

没有不依赖其他模块的好方法。使用NumPy相当简单,只要内存视图(memoryview)没有 suboffsets 即可:

transposed = memoryview(numpy.asarray(orig_memoryview).T)

orig_memoryview 可以由任何东西支持 - 不一定需要有一个 NumPy 数组支持它。

与其他答案不同,生成的 memoryview 由与原始 memoryview 相同的内存支持。例如,对于以下多维 memoryview:

In [1]: import numpy

In [2]: arr = numpy.array([[1, 2], [3, 4]])

In [3]: mem = memoryview(arr)

我们可以转置它:

In [4]: transposed = memoryview(numpy.asarray(mem).T)

并且对转置后的内存视图的写入会影响原始数组:
In [5]: transposed[0, 1] = 5

In [6]: arr
Out[6]: 
array([[1, 2],
       [5, 4]])

在这里,向转置的第0行第1列写入相当于向原始数组的第1行第0列写入。
这不依赖于原始memoryview由NumPy数组支持。它可以很好地处理由其他东西支持的memoryview,比如bytearray:
In [7]: x = bytearray([1, 2, 3, 4])

In [8]: y = memoryview(x).cast('b', (2, 2))

In [9]: transposed = memoryview(numpy.asarray(y).T)

In [10]: transposed[0, 1] = 5

In [11]: y[1, 0]
Out[11]: 5

In [12]: x
Out[12]: bytearray(b'\x01\x02\x05\x04')

没有NumPy或类似的依赖项,我看不到好的方法。最接近好方法的是使用ctypes,但您需要为此硬编码Py_buffer结构布局,并且Py_buffer结构的确切布局未经记录。(字段顺序和类型文档中记录的字段顺序或文档中记录的类型不完全匹配。)此外,对于带有子偏移量的PIL-style数组,没有办法在不复制数据的情况下转置memoryview。

好消息是,大多数涉及多维memoryviews的情况下,您已经拥有了转置它们所需的依赖项。


感谢您的回答!值得一提的是,似乎有一个Python错误报告讨论了转置memoryview或更准确地处理F-order memoryview的可能性。https://bugs.python.org/issue35845 - jakirkham

1
这可能会对你有所帮助:

>>> import numpy as np
>>> import array
>>> a = array.array('l', [-11111111, 22222222, -33333333, 44444444])
>>> m = memoryview(a)
>>> m_copy =  np.array(m)[np.newaxis]
>>> m_copy
array([[-11111111,  22222222, -33333333,  44444444]])
>>> m_copy.T
array([[-11111111],
       [ 22222222],
       [-33333333],
       [ 44444444]])


不使用numpy:

import array
a = array.array('l', [-11111111, 22222222, -33333333, 44444444])
print(a)

#output:
array('l', [-11111111, 22222222, -33333333, 44444444])

m = memoryview(a)
a = [[x for x in m]]
result = list(map(list, zip(*a)))
print(result)

#output:
[[-11111111], [22222222], [-33333333], [44444444]]


要理解这部分[np.newaxis],请阅读此SO答案:https://dev59.com/EG025IYBdhLWcg3whWdm#5954747 - Sabito stands with Ukraine
谢谢。是否可以在不使用NumPy的情况下完成这个任务?也就是说,在Python标准库中实现? - jakirkham
list(map(list, zip(*a))) 从 https://dev59.com/42w15IYBdhLWcg3wqNcA#6473724 获取。 - Sabito stands with Ukraine
这实际上并没有转置memoryview。它构建了一个由新缓冲区支持的NumPy数组,并对其进行了转置。 - user2357112

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接