从memory对象看Numpy中的ndarray对象

memoryview对象

  • 内存视图:简化一句话就是在不copy数据的情况下,与其他对象能够共享同一个内存地址,达到操作数据的目的,在处理大量数据的时候能够极大降低内存的开销。这个类的概念灵感来自于Numpy的数组。Numpy作者回答
    • memoryview对象属性及方法:仅仅涉及部分方法属性
import array
mev= memoryview(array.array('i',[1,2,3])) # memoryview(obj) obj必须是实现了缓冲协议的对象 例如bytes(字节)、bytearray、array.array
print(mev) #打印memor对象  <memory at 0x114293ac8>
print(mev.tolist()) # [1, 2, 3] obj的列表形式
print(mev.tobytes()) # b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00'
# 接上:返回字节串形式,因为在array中字节类型码是i 代表signed int(有符号整数) 2个字节  打印出 每个元素字节其实是4 bytes,
#接上:16进制表示那就是1 代表是 b'\x01\x00\x00\x00
# 接上:返回的形式中存在小端 1 16进制 4字节表示其实是:0x00 0x00 0x00 0x01
print(mev.hex()) # 两位16进制  因为是4字节  表示buffer中的数据 1(01 00 00 00) 2 (02 00 00 00)3(03 00 00 00)

c= array.array('i',[1,2000,3])
print(c.itemsize) #打印出 每个元素字节可以看出其实是4 bytes
print(c.tobytes()) # b'\x01\x00\x00\x00\xd0\x07\x00\x00\x03\x00\x00\x00' 同memoryview

  • memory对数据的修改:
      import array
      c = array.array('i',[1,2,3]) 
      mev= memoryview(c) # memoryview(obj) obj必须是实现了缓冲协议的对象 例如bytes(字节)、bytearray、array.array
      mev[1] =4  #mev支持index slicing
      print(mev[1]) # 整数索引 返回对应位置元素值 1
      print('第一次修改:',c)   # array('i', [1, 1, 3]) 原生数组c索引位置为1的已经被修改

      k = mev[0:1] #memoryview支持切片 返回对象仍然是 memoryview
      print(k) # <memory at 0x1133d0a08>
      
import array
c = array.array('i',[1,2,3])
mev= memoryview(c) # memoryview(obj) obj必须是实现了缓冲协议的对象 例如bytes(字节)、bytearray、array.array

print(c.itemsize) # 4
print(mev.itemsize) # mev中内容每个元素大小均是4个字节


mev_cast= mev.cast('B') # cast 函数 返回一个memoryview 并将原来的视图里面的内容进行类型转换 转换成无符号 此时每个元素只有1个字节
print(mev_cast.tolist()) # 未修改 索引3之前 [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0]
mev_cast[3]=4  # 
print(mev_cast.itemsize)  # 1  mev_cast中每个元素大小均为1bytes

print('mev_cast :',mev_cast.hex()) # 输出结果  010000040200000003000000
print('mev:',mev.hex())  # 输出结果  010000040200000003000000

print('kaishi1',c) # 输出结果 kaishi1 array('i', [67108865, 2, 3])
print(mev_cast.tolist())  # [1, 0, 0, 4, 2, 0, 0, 0, 3, 0, 0, 0] # 之前是4个字节为1个单位 现在被拆分成1个字节为单位,总共12个元素 = 3*4,上述mev_cast[3]=4 修改的其实是该列表
对于其他类型属性需要参照各自存储方式:有符号数存在符号位,并且在计算机内存中储存的是补码。

ndarry构造方法

  • numpy中常见构造方法各种博客都有介绍,今天写一下ndarry这个在官方中成为low-level构造ndarry对象的方法,在探究的过程还发现了ndarry与memoryview对象其中不少的关系,写下本文放方便以后记录学习。
    • numpy.ndarry(shape,buffer,offset,dtype,strides,order):各参数解释如下:
      1.shape: 由整数组成的元组,表示创建的ndarry对象的形状,其实就是数组在各个轴上或者维度上的元素的个数;

      2.buffer:暴露缓冲接口的对象 用数据填充这一array数组。当我们需要buffer缓冲数据由其他来源来创建,并且这一来源还不是可以被重塑(reshape)以及view的ndarry ;这一观点来自于用指定数据创建ndarry对象
      3.offset:在buffer数据中偏移量,其实也是指代了buffer中数据的开始的位置
      4.dtype:数组中数据类型
      5.strudes:跨度
      6.order:‘C’--代表行优先,‘F’代表列优先

问题背景

  • buffer在创建ndarray中的作用
    • buffer为None的时候:输出的数组是随机 random
import  numpy as np
k = np.ndarray(shape=(4,2), dtype=int, order='c')
print(k)
out: 
[[-8070450532247928832 -8070450532247928832]
 [ 8027794400713703428  7811887657498193774]
 [ 7815259820786999141  8245937481777164148]
 [ 7310584009609916025     1125904217407488]]
  • buffer不为None的时候:
import numpy as np
k = np.array([1,2,3,4])
num = np.ndarray(shape=(2,1),buffer=k,dtype=int,offset=1)  
# num = np.ndarray(shape=(2,1),buffer=k.data,dtype=int,offset=1)  上述中传入buffer的其实是作为ndarray对象k中的data (Python buffer object pointing to the start of the array’s data.)
print(k.data.__class__) # <class 'memoryview'> 代表一个memory对象

import numpy as np
k = np.array([1,2,3,4])
# 现在来分别测试 buffer 以及offset
#buffer
num_buffer = np.ndarray(shape=(2,1),buffer=k,dtype=int)

print(num_buffer.data.hex()) # 01000000000000000200000000000000
print(num_buffer.tolist()) # [[1], [2]]
print(num_buffer.tobytes()) # b'\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00' 小端模式返回字节数据
print(num_buffer.itemsize) # 每个元素对应大小是8个字节 [1]对应字节是 :b'\x01\x00\x00\x00\x00\x00\x00\x00 内存存储形式 同上

# buffer and offset

num = np.ndarray(shape=(2,1),buffer=k,dtype=int,offset=1)

print(num.data.hex()) # 00000000000000020000000000000003
print(num.tolist()) # [[144115188075855872], [216172782113783808]]
print(num.tobytes()) # b'\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03'小端模式返回字节数据
print(num.itemsize) # 每个元素对应大小是8个字节 [144115188075855872]对应字节是 :b'\x00\x00\x00\x00\x00\x00\x00\x02 内存存储形式 同上


import struct
value = struct.unpack('<q',b'\x00\x00\x00\x00\x00\x00\x00\x02') #将字节按照指定格式转换为数值,因为需要转化成8个字节大小的数据 所以数据类型选用了'q'
print(value) # value 144115188075855872

# 在num中每个数据类型是int64位,8个字节,以下分别代表offset不同偏移位置
#-2b'\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00'
#-1b'\x00\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00'
#  b'\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00
#0 b'\x01\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00'
#1 b'\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03'
#2 b'\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00'
#3 b'\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00'
#4 b'\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00'
#5 b'\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00'
#6 b'\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00'
#7 b'\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00'
#8 b'\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00'
#9 b'\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x04'
#10 b'\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x04\x00'
#11 b'\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00'
12 b'\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00'
13 b'\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00'
'''
#当偏移量是itemsize =8 的整数倍的时候就相当于对对buffer数据源进行切片操作
nu1 = np.ndarray(shape=(2,1),buffer=k,dtype=int,offset=8)
print(nu1) # [[2] [3]]
value =  k[1:3].reshape(2,1) #偏移8个字节 相当于1个元素,所以起始所以为1,总共包含2个元素 end-1=2  即为3
print(value) # [[2] [3]]

总结

虽然创建ndarry对象基本不会使用numpy.ndarray这个创造对象方法,但是对于背后的了解其实发现涉及到缓冲区、字节数据转换、计算机存储形式、内存视图(view)概念有很多帮助。
在查看numpy概念中了解到背后使用了C语言中指针 结构体,所以接下来将会进一步深化对概念的理解。

上一篇:node服务器如何接受硬件发来的TCP/IP数据


下一篇:内建模块-base64、struct