• 从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'x01x00x00x00x02x00x00x00x03x00x00x00'
    # 接上:返回字节串形式,因为在array中字节类型码是i 代表signed int(有符号整数) 2个字节  打印出 每个元素字节其实是4 bytes,
    #接上:16进制表示那就是1 代表是 b'x01x00x00x00
    # 接上:返回的形式中存在小端 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'x01x00x00x00xd0x07x00x00x03x00x00x00' 同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'x01x00x00x00x00x00x00x00x02x00x00x00x00x00x00x00' 小端模式返回字节数据
    print(num_buffer.itemsize) # 每个元素对应大小是8个字节 [1]对应字节是 :b'x01x00x00x00x00x00x00x00 内存存储形式 同上
    
    # 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'x00x00x00x00x00x00x00x02x00x00x00x00x00x00x00x03'小端模式返回字节数据
    print(num.itemsize) # 每个元素对应大小是8个字节 [144115188075855872]对应字节是 :b'x00x00x00x00x00x00x00x02 内存存储形式 同上
    
    
    import struct
    value = struct.unpack('<q',b'x00x00x00x00x00x00x00x02') #将字节按照指定格式转换为数值,因为需要转化成8个字节大小的数据 所以数据类型选用了'q'
    print(value) # value 144115188075855872
    
    # 在num中每个数据类型是int64位,8个字节,以下分别代表offset不同偏移位置
    #-2b'x00x00x01x00x00x00x00x00x00x00x02x00x00x00x00x00'
    #-1b'x00x01x00x00x00x00x00x00x00x02x00x00x00x00x00x00'
    #  b'x01x00x00x00x00x00x00x00x02x00x00x00x00x00x00x00x03x00x00x00x00x00x00x00x04x00x00x00x00x00x00x00
    #0 b'x01x00x00x00x00x00x00x00x02x00x00x00x00x00x00x00'
    #1 b'x00x00x00x00x00x00x00x02x00x00x00x00x00x00x00x03'
    #2 b'x00x00x00x00x00x00x02x00x00x00x00x00x00x00x03x00'
    #3 b'x00x00x00x00x00x02x00x00x00x00x00x00x00x03x00x00'
    #4 b'x00x00x00x00x02x00x00x00x00x00x00x00x03x00x00x00'
    #5 b'x00x00x00x02x00x00x00x00x00x00x00x03x00x00x00x00'
    #6 b'x00x00x02x00x00x00x00x00x00x00x03x00x00x00x00x00'
    #7 b'x00x02x00x00x00x00x00x00x00x03x00x00x00x00x00x00'
    #8 b'x02x00x00x00x00x00x00x00x03x00x00x00x00x00x00x00'
    #9 b'x00x00x00x00x00x00x00x03x00x00x00x00x00x00x00x04'
    #10 b'x00x00x00x00x00x00x03x00x00x00x00x00x00x00x04x00'
    #11 b'x00x00x00x00x00x03x00x00x00x00x00x00x00x04x00x00'
    12 b'x00x00x00x00x03x00x00x00x00x00x00x00x04x00x00x00'
    13 b'x00x00x00x03x00x00x00x00x00x00x00x04x00x00x00x00'
    '''
    #当偏移量是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语言中指针 结构体,所以接下来将会进一步深化对概念的理解。

  • 相关阅读:
    防御式编程
    Linux磁盘与文件系统管理
    更加抽象
    高质量的子程序
    Linux文件与目录管理
    抽象
    可以工作的类
    Linux的文件权限与目录配置
    条件、循环和其他语句
    软件构建中的设计
  • 原文地址:https://www.cnblogs.com/ivan09/p/14234821.html
Copyright © 2020-2023  润新知