Python Numpy data-type dtype 自定义数据类型

本文详细介绍了NumPy中数据类型的定义、使用及转换方法。包括BIG-ENDIAN与LITTLE_ENDIAN的区别,自定义复杂数据类型的方法,以及如何利用dtype参数创建不同格式的数据类型。此外还讲解了如何通过astype函数进行数据类型转换。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、实例

BIG-ENDIAN和LITTLE_ENDIAN区别
数据类型定义:

>>> dt = np.dtype('>i4')  定义一个big-endian int 4*8=32位的数据类型
>>> dt
dtype('>i4')
>>> dt.byteorder    //字节顺序:>为big-edian <为little-endian 
'>'
>>> dt.itemsize    //字节大小
4
>>> dt.name       //dt类型
'int32'
>>> dt.type is np.int32
True

自定义数据类型:
定义dt:

>>> dt = np.dtype([('name', np.str_, 16), ('grades', np.float64, (2,))])   //定义一个数据类型,其中name为16为字符串,grades为2个float64的子数组
>>> dt['name']
dtype('<U16')
>>> dt['grades']
dtype(('<f8',(2,)))

使用:

>>> x = np.array([('Sarah', (8.0, 7.0)), ('John', (6.0, 7.0))], dtype=dt)
>>> x[1]
('John', [6.0, 7.0])
>>> x[1]['grades']
array([ 6.,  7.])
>>> type(x[1])
<type 'numpy.void'>
>>> type(x[1]['grades'])
<type 'numpy.ndarray'>

二、dtype参数

在声明数据类型时dtype能够自动将参数转为相应类型。默认数据类型为float_。
24个內建参数:
24內建Array-scalar types

>>> dt = np.dtype(np.int32)      # 32位int,注意32为位
>>> dt = np.dtype(np.complex128) # 128位复数

numpy.sctypeDict.keys()参数:
存在于numpy.sctypeDict.keys()中的字符串参数:

>>> dt = np.dtype('uint32')   # 32位uint,注意32为位
>>> dt = np.dtype('Float64')  # 64位float

python类型参数:

TablesAre
intint_
boolbool_
floatfloat_
complexcfloat
strstring
unicodeunicode_
buffervoid
(all others)object_
>>> dt = np.dtype(float)   # Python的浮点
>>> dt = np.dtype(int)     # Python的整型
>>> dt = np.dtype(object)  # Python的对象

简略字符参数:

'b'     boolean
'i'     (signed) integer
'u'     unsigned integer
'f'     floating-point
'c'     complex-floating point
'm'     timedelta
'M'     datetime
'O'     (Python) objects
'S', 'a'    (byte-)string
'U'     Unicode
'V'     raw data (void)
>>> dt = np.dtype('f8')   # 64位浮点,注意8为字节
>>> dt = np.dtype('c16')  # 128位复数   

带逗号字符串参数:

>>> dt = np.dtype("a3, 3u8, (3,4)a10")  //3字节字符串、3个64位整型子数组、3*4的10字节字符串数组,注意8为字节

其他:
(flexible_dtype, itemsize)第一个参数类型参数大小不固定,第二传入大小:

>>> dt = np.dtype((void, 10))  #10位
>>> dt = np.dtype((str, 35))   # 35字符字符串
>>> dt = np.dtype(('U', 10))   # 10字符unicode string

(fixed_dtype, shape)第一个参数传入固定大小参数,第二参数传入个数

>>> dt = np.dtype((np.int32, (2,2)))          # 2*2int子数组
>>> dt = np.dtype(('S10', 1))                 # 10字符字符串
>>> dt = np.dtype(('i4, (2,3)f8, f4', (2,3))) # 2x3结构子数组

[(field_name, field_dtype, field_shape), …]:

>>> dt = np.dtype([('big', '>i4'), ('little', '<i4')])
>>> dt = np.dtype([('R','u1'), ('G','u1'), ('B','u1'), ('A','u1')])

{‘names’: …, ‘formats’: …, ‘offsets’: …, ‘titles’: …, ‘itemsize’: …}:

>>> dt = np.dtype({'names': ['r','g','b','a'],'formats': [uint8, uint8, uint8, uint8]})
>>> dt = np.dtype({'names': ['r','b'], 'formats': ['u1', 'u1'], 'offsets': [0, 2],'titles': ['Red pixel', 'Blue pixel']})

{‘field1’: …, ‘field2’: …, …}:
不推荐使用,可能会产生冲突

>>> dt = np.dtype({'col1': ('S10', 0), 'col2': (float32, 10),'col3': (int, 14)}) //col1在字节0处,col2在字节10处,col3在字节14处

(base_dtype, new_dtype):

>>> dt = np.dtype((np.int32,{'real':(np.int16, 0),'imag':(np.int16, 2)})  //base_dtype前两个字节放置real,后两个字节放置imag
>>>dt = np.dtype((np.int32, (np.int8, 4)))  //base_dtype被分成4个int8的子数组

三、切换类型

使用astype,不可直接更改对象的dtype值

>>> b = np.array([1., 2., 3., 4.])
>>> b.dtype
dtype(‘float64‘)
>>> c = b.astype(int)
>>> c
array([1, 2, 3, 4])
>>> c.shape
(8,)
>>> c.dtype
dtype(‘int32‘)
>>> b
array([ 1.,  2.,  3.,  4.])
>>> b.dtype = ‘int‘
>>> b.dtype
dtype(‘int32‘)
>>> b
array([0, 1072693248,0, 1073741824,0,1074266112,          0, 1074790400])  //数组长度加倍
>>> b.shape
(8,)

查看类型取值范围:

np.finfo(np.float32)

英文版

<think>我们面临两个主要挑战:1.将多个共享内存块合并到单个`mp.RawArray`中,以便一次性创建并传递给子进程。2.支持自定义numpydtype(结构化数据类型)的共享内存处理。解决方案思路:1.**合并共享内存**:计算每个内存块所需的大小(考虑对齐),然后分配一个足够大的`RawArray`(类型为`ctypes.c_byte`),再通过偏移量将各个内存块映射到这个大数组中。2.**自定义dtype处理**:将numpy自定义dtype转换为ctypes结构体,以便在共享内存中表示。步骤详解:###步骤1:定义自定义dtype并转换为ctypes假设我们有一个自定义dtype:```pythoncustom_dtype=np.dtype([('id',np.int32),('position',np.float64,(3,)),('velocity',np.float64,(3,))])```我们需要将其转换为ctypes结构体:```pythonclassParticle(ctypes.Structure):_fields_=[('id',ctypes.c_int32),('position',ctypes.c_double*3),('velocity',ctypes.c_double*3)]```###步骤2:计算内存布局我们需要为每个共享内存块计算偏移量。注意内存对齐(alignment)问题。ctypes结构体默认会进行对齐,我们可以通过`ctypes.sizeof`和`ctypes.alignment`来获取大小和对齐要求。定义内存块配置:```pythonblocks_config={'particles':{'dtype':Particle,#ctypes类型'count':1000#元素个数},'temperature':{'dtype':ctypes.c_double,'count':500}}```计算总大小和每个块的偏移量:```pythondefcalculate_layout(blocks_config):current_offset=0layout={}forname,configinblocks_config.items():dtype=config['dtype']count=config['count']#计算对齐要求align=ctypes.alignment(dtype)#调整当前偏移量以满足对齐current_offset=(current_offset+align-1)//align*align#记录该块的偏移量layout[name]={'offset':current_offset,'size':ctypes.sizeof(dtype)*count,'dtype':dtype,'count':count}current_offset+=layout[name]['size']total_size=current_offsetreturnlayout,total_size```###步骤3:创建合并的共享内存```pythonlayout,total_size=calculate_layout(blocks_config)#创建一个大数组,类型为字节,大小为total_sizeshared_mem=mp.RawArray(ctypes.c_byte,total_size)```###步骤4:为每个块创建视图```pythondefcreate_views(shared_mem,layout):views={}forname,block_infoinlayout.items():#计算起始指针start=block_info['offset']#获取该块的ctypes类型ctype=block_info['dtype']count=block_info['count']#创建类型化视图#注意:这里使用from_buffer,共享内存必须可写(RawArray可写)array_type=ctype*count#从shared_mem的指定偏移处创建视图view=array_type.from_buffer(shared_mem,start)views[name]=viewreturnviews```###步骤5:在进程间传递共享内存主进程创建共享内存和视图后,将共享内存对象传递给子进程。子进程使用相同的`layout`信息重建视图。###步骤6:使用numpy访问自定义dtype数据对于自定义dtype,我们可以将ctypes数组转换为numpy数组,同时指定dtype。注意:我们需要确保numpydtype与ctypes结构体的内存布局一致。在创建numpy数组视图时:```python#主进程或子进程中particle_view=views['particles']#这是一个Particle数组#转换为numpy数组particle_np=np.ctypeslib.as_array(particle_view).view(custom_dtype)#但注意:上面的view(custom_dtype)可能会因为对齐问题而失败,所以最好在定义ctypes结构体时确保与numpydtype的内存布局一致。#更安全的方式:直接从共享内存的偏移处创建numpy数组#首先获取共享内存的地址buf=memoryview(shared_mem).cast('B')#转为字节视图#对于'particles'块,我们根据layout中的信息创建start=layout['particles']['offset']size=layout['particles']['size']#从字节缓冲区创建numpy数组particle_np=np.frombuffer(buf,dtype=custom_dtype,count=1000,offset=start)```###完整示例代码####主进程```pythonimportmultiprocessingasmpimportctypesimportnumpyasnp#定义numpy自定义dtypecustom_dtype=np.dtype([('id',np.int32),('position',np.float64,(3,)),('velocity',np.float64,(3,))])#定义对应的ctypes结构体classParticle(ctypes.Structure):_fields_=[('id',ctypes.c_int32),('position',ctypes.c_double*3),('velocity',ctypes.c_double*3)]#配置共享内存块blocks_config={'particles':{'dtype':Particle,'count':1000},'temperature':{'dtype':ctypes.c_double,'count':500}}defcalculate_layout(blocks_config):current_offset=0layout={}forname,configinblocks_config.items():dtype=config['dtype']count=config['count']#对齐要求align=ctypes.alignment(dtype)#调整当前偏移以满足对齐ifcurrent_offset%align!=0:current_offset+=align-(current_offset%align)layout[name]={'offset':current_offset,'size':ctypes.sizeof(dtype)*count,'dtype':dtype,'count':count}current_offset+=layout[name]['size']total_size=current_offsetreturnlayout,total_sizedefcreate_views(shared_mem,layout):views={}forname,block_infoinlayout.items():start=block_info['offset']ctype=block_info['dtype']count=block_info['count']array_type=ctype*count#从共享内存的偏移位置创建视图view=array_type.from_buffer(shared_mem,start)views[name]=viewreturnviewsdefworker(shared_mem,layout,proc_id):#重建视图views=create_views(shared_mem,layout)#访问粒子数据(通过ctypes)particle_ct=views['particles']#修改第一个粒子的idparticle_ct[0].id=proc_id#或者使用numpy方式访问(推荐,尤其对于结构化数据)buf=memoryview(shared_mem).cast('B')#获取粒子块的起始偏移和数量start=layout['particles']['offset']count=layout['particles']['count']particle_np=np.frombuffer(buf,dtype=custom_dtype,count=count,offset=start)#修改第二个粒子的位置particle_np[1]['position']=[1.0,2.0,3.0]if__name__=='__main__':layout,total_size=calculate_layout(blocks_config)shared_mem=mp.RawArray(ctypes.c_byte,total_size)#在主进程创建视图(可选)views=create_views(shared_mem,layout)#启动子进程p=mp.Process(target=worker,args=(shared_mem,layout,123))p.start()p.join()#主进程检查子进程修改的结果#使用numpy方式查看粒子数据buf=memoryview(shared_mem).cast('B')particle_np=np.frombuffer(buf,dtype=custom_dtype,count=1000,offset=layout['particles']['offset'])print("子进程修改后的第一个粒子id:",particle_np[0]['id'])print("子进程修改后的第二个粒子位置:",particle_np[1]['position'])```###注意事项1.**内存对齐**:我们在计算偏移量时进行了对齐调整,确保每个块从对齐的地址开始,避免性能下降或某些架构上的错误。2.**进程安全**:当多个进程同时写入同一内存区域时,需要同步机制(如锁)来避免竞争条件。本示例未包含同步,实际应用需添加。3.**numpy与ctypes的匹配**:自定义dtypenumpy和ctypes中的定义必须保证内存布局一致(字段顺序、类型、对齐)。上述示例中,我们确保`Particle`结构体与`custom_dtype`描述一致。###性能优化-对于大型数组,使用numpy的向量化操作而不是逐元素操作。-避免频繁创建临时内存视图,尽量复用。###替代方案如果不需要合并多个共享内存块,也可以为每个块单独创建`mp.RawArray`,然后传递给子进程。但合并内存块可以减少系统调用次数,并可能提高内存局部性。###相关问题§§相关问题§§1.如何确保自定义numpydtype与ctypes结构体的内存布局完全一致?2.多进程同时写入共享内存时,如何设计同步机制避免竞争?3.在共享内存中处理大型numpy数组时,如何优化访问性能?4.如何动态扩展共享内存的大小?</think># 使用 `mp.RawArray` 实现多块共享内存合并与自定义 numpy dtype 处理 我将为您提供完整的解决方案,实现多个共享内存块的合并、进程间通信,并支持自定义 numpy dtype 数据。 ## 核心解决方案 ### 1. 自定义 numpy dtype 转换为 ctypes 结构体 首先,我们需要将自定义 numpy dtype 映射到 ctypes 结构体: ```python import numpy as np import ctypes import multiprocessing as mp # 定义自定义 numpy dtype custom_dtype = np.dtype([ ('id', np.int32), ('position', np.float64, (3,)), ('velocity', np.float64, (3,)), ('flags', np.uint8, 4) ]) # 将 numpy dtype 转换为 ctypes 结构体 class CustomType(ctypes.Structure): _fields_ = [ ('id', ctypes.c_int32), ('position', ctypes.c_double * 3), ('velocity', ctypes.c_double * 3), ('flags', ctypes.c_uint8 * 4) ] # 验证内存布局匹配 assert ctypes.sizeof(CustomType) == custom_dtype.itemsize ``` ### 2. 多共享内存块合并与布局管理 ```python class SharedMemoryManager: def __init__(self): self.blocks = {} # 存储内存块信息 self.total_size = 0 def add_block(self, name, ctype, count): """添加新的内存块""" # 计算对齐偏移(64字节缓存行对齐) align = 64 offset = (self.total_size + align - 1) // align * align size = ctypes.sizeof(ctype) * count self.blocks[name] = { 'ctype': ctype, 'count': count, 'offset': offset, 'size': size } self.total_size = offset + size def create_shared_memory(self): """创建合并的共享内存""" # 创建字节数组作为基础内存 shared_mem = mp.RawArray(ctypes.c_byte, self.total_size) return shared_mem def get_view(self, shared_mem, name): """获取指定内存块的视图""" block = self.blocks[name] # 创建类型化视图 array_type = block['ctype'] * block['count'] return array_type.from_buffer(shared_mem, block['offset']) def get_numpy_view(self, shared_mem, name, dtype): """获取numpy数组视图""" block = self.blocks[name] # 使用memoryview避免数据复制 mem_view = memoryview(shared_mem).cast('B') start = block['offset'] end = start + block['size'] # 直接创建numpy数组视图 return np.frombuffer(mem_view[start:end], dtype=dtype) ``` ### 3. 进程间通信实现 #### 主进程设置 ```python def main(): # 创建内存管理器 mem_manager = SharedMemoryManager() # 添加内存块 mem_manager.add_block('particles', CustomType, 1000) # 自定义类型数据 mem_manager.add_block('temperature', ctypes.c_double, 500) # 普通数组 mem_manager.add_block('flags', ctypes.c_uint32, 200) # 标志位数组 # 创建共享内存 shared_mem = mem_manager.create_shared_memory() # 初始化数据 particles_np = mem_manager.get_numpy_view(shared_mem, 'particles', custom_dtype) particles_np['id'] = np.arange(1000) particles_np['position'] = np.random.rand(1000, 3) # 创建进程锁(用于同步) lock = mp.Lock() # 启动工作进程 processes = [] for i in range(4): p = mp.Process(target=worker_process, args=(shared_mem, mem_manager.blocks, i, lock)) processes.append(p) p.start() # 等待所有进程完成 for p in processes: p.join() # 验证结果 print("处理后的粒子平均速度:", particles_np['velocity'].mean(axis=0)) ``` #### 工作进程实现 ```python def worker_process(shared_mem, blocks_config, proc_id, lock): """工作进程处理函数""" # 重建内存视图 particles_view = (blocks_config['particles']['ctype'] * blocks_config['particles']['count']).from_buffer( shared_mem, blocks_config['particles']['offset']) # 使用numpy视图更方便处理 mem_view = memoryview(shared_mem).cast('B') particles_np = np.frombuffer( mem_view[blocks_config['particles']['offset']: blocks_config['particles']['offset'] + blocks_config['particles']['size']], dtype=custom_dtype ) # 计算本进程处理的数据范围 chunk_size = len(particles_np) // 4 start = proc_id * chunk_size end = start + chunk_size if proc_id < 3 else len(particles_np) # 处理数据(示例:更新速度) for i in range(start, end): # 需要同步的操作使用锁 with lock: particles_np[i]['velocity'] = particles_np[i]['position'] * 0.5 # 无需同步的操作可直接进行 particles_np[i]['flags'][0] = proc_id # 处理其他内存块(示例:温度数据) temp_view = (blocks_config['temperature']['ctype'] * blocks_config['temperature']['count']).from_buffer( shared_mem, blocks_config['temperature']['offset']) # 使用numpy处理温度数据 temp_np = np.frombuffer( mem_view[blocks_config['temperature']['offset']: blocks_config['temperature']['offset'] + blocks_config['temperature']['size']], dtype=np.float64 ) temp_np[start:end] = proc_id * 10.0 ``` ## 关键技术详解 ### 1. 内存对齐优化 多内存块合并的关键是正确处理内存对齐: - **缓存行对齐**:64字节对齐避免 false sharing - **结构体对齐**:ctypes 结构体自动处理字段对齐 - **计算公式**: 偏移量计算:$ \text{offset}_{n} = \left\lceil \frac{\text{offset}_{n-1} + \text{size}_{n-1}}{\text{align}} \right\rceil \times \text{align} $ ### 2. 自定义 dtype 处理流程 ```mermaid graph TD A[定义numpy dtype] --> B[创建对应ctypes结构体] B --> C[验证内存布局一致性] C --> D[在共享内存中分配空间] D --> E[使用frombuffer创建numpy视图] E --> F[直接操作numpy数组] ``` ### 3. 进程同步策略 | 同步机制 | 适用场景 | 实现方式 | |----------------|-------------------------|----------------------------| | **区域划分** | 数据并行处理 | 预先分配进程专属操作区间 | | **互斥锁** | 精细控制的小范围操作 | `with lock:` 保护临界区 | | **原子操作** | 简单计数器更新 | `ctypes` 原子类型 | | **屏障同步** | 多阶段处理同步 | `mp.Barrier` | ## 高级应用:动态内存管理 对于需要动态扩容的场景,可使用分层内存管理: ```python class DynamicMemoryManager: def __init__(self, base_size=1024): self.base = mp.RawArray(ctypes.c_byte, base_size) self.blocks = {} self.free_list = [(0, base_size)] def allocate(self, name, ctype, count): size = ctypes.sizeof(ctype) * count # 寻找合适的内存空隙(首次适应算法) for i, (start, free_size) in enumerate(self.free_list): if free_size >= size: # 分配内存 self.blocks[name] = {'start': start, 'size': size} # 更新空闲列表 if free_size > size: self.free_list[i] = (start + size, free_size - size) else: del self.free_list[i] return (ctype * count).from_buffer(self.base, start) # 空间不足时扩展内存 self._expand_memory(size) return self.allocate(name, ctype, count) def _expand_memory(self, min_size): # 扩展内存实现(略) pass ``` ## 性能优化策略 1. **访问局部性优化**: ```python # 低效访问 for i in range(len(data)): process(data[i]) # 高效访问(连续内存块) chunk = data[start:end] result = vectorized_process(chunk) data[start:end] = result ``` 2. **批处理减少锁竞争**: ```python # 每次操作都加锁(低效) for item in data: with lock: update(item) # 批量处理加锁(高效) with lock: batch_update(data_chunk) ``` 3. **内存布局优化**: - 将高频访问数据放在内存开头 - 将同时访问的数据块相邻存放 - 冷热数据分离存储 ## 常见问题解决 1. **内存布局不一致**: ```python # 检查numpy dtype与ctypes结构体大小 if custom_dtype.itemsize != ctypes.sizeof(CustomType): # 添加填充字段使其匹配 class CustomType(ctypes.Structure): _fields_ = [ ('id', ctypes.c_int32), ('_pad1', ctypes.c_byte * 4), # 填充 ('position', ctypes.c_double * 3), ... ] ``` 2. **跨平台对齐问题**: ```python # 强制指定结构体对齐方式 class CustomType(ctypes.Structure): _pack_ = 8 # 8字节对齐 _fields_ = [...] ``` ## 替代方案对比 | 方法 | 优点 | 缺点 | |---------------------|-------------------------------|-------------------------------| | **单RawArray合并** | 零复制访问,高性能 | 需手动管理偏移量 | | **共享内存管理器** | 自动管理 | 有序列化开销 | | **mmap文件映射** | 支持超大内存 | 需文件系统支持 | | **Redis共享内存** | 支持跨机器 | 需要额外服务 | > **关键点**:对于高性能自定义 dtype 处理,`RawArray` 合并配合 `numpy.frombuffer` 是最佳方案,可实现零复制内存访问[^1]。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值