zoukankan      html  css  js  c++  java
  • python struct 结构体

    import struct

    有的时候需要用python处理二进制数据,比如,存取文件,socket操作时.这时候,可以使用python的struct模块来完成.可以用 struct来处理c语言中的结构体.

    struct模块中最重要的三个函数是pack(), unpack(), calcsize()

    pack(fmt, v1, v2, ...)     按照给定的格式(fmt),把数据封装成字符串(实际上是类似于c结构体的字节流)

    unpack(fmt, string)       按照给定的格式(fmt)解析字节流string,返回解析出来的tuple

    calcsize(fmt)                 计算给定的格式(fmt)占用多少字节的内存
     

    struct中支持的格式如下表:

    Format C Type Python 字节数
    x pad byte no value 1
    c char string of length 1 1
    b signed char integer 1
    B unsigned char integer 1
    ? _Bool bool 1
    h short integer 2
    H unsigned short integer 2
    i int integer 4
    I unsigned int integer or long 4
    l long integer 4
    L unsigned long long 4
    q long long long 8
    Q unsigned long long long 8
    f float float 4
    d double float 8
    s char[] string 1
    p char[] string 1
    P void * long

    注1.q和Q只在机器支持64位操作时有意思

    注2.每个格式前可以有一个数字,表示个数

    注3.s格式表示一定长度的字符串,4s表示长度为4的字符串,但是p表示的是pascal字符串

    注4.P用来转换一个指针,其长度和机器字长相关

    注5.最后一个可以用来表示指针类型的,占4个字节
     

    为了同c中的结构体交换数据,还要考虑有的c或c++编译器使用了字节对齐,通常是以4个字节为单位的32位系统,故而struct根据本地机器字节顺序转换.可以用格式中的第一个字符来改变对齐方式.定义如下:

    Character Byte order Size and alignment
    @ native native            凑够4个字节
    = native standard        按原字节数
    < little-endian standard        按原字节数
    > big-endian standard       按原字节数
    ! network (= big-endian)

    standard       按原字节数

    使用方法是放在fmt的第一个位置,就像'@5s6sif'
     

    示例一:

    比如有一个结构体

    struct Header
    
    {
    
        unsigned short id;
    
        char[4] tag;
    
        unsigned int version;
    
        unsigned int count;
    
    }

    通过socket.recv接收到了一个上面的结构体数据,存在字符串s中,现在需要把它解析出来,可以使用unpack()函数.

    import struct
    
    id, tag, version, count = struct.unpack("!H4s2I", s)

    上面的格式字符串中,!表示我们要使用网络字节顺序解析,因为我们的数据是从网络中接收到的,在网络上传送的时候它是网络字节顺序的.后面的H表示 一个unsigned short的id,4s表示4字节长的字符串,2I表示有两个unsigned int类型的数据.

    就通过一个unpack,现在id, tag, version, count里已经保存好我们的信息了.

    同样,也可以很方便的把本地数据再pack成struct格式.

    ss = struct.pack("!H4s2I", id, tag, version, count);

    pack函数就把id, tag, version, count按照指定的格式转换成了结构体Header,ss现在是一个字符串(实际上是类似于c结构体的字节流),可以通过 socket.send(ss)把这个字符串发送出去.


    示例二:

    import struct
    
    a=12 #将a变为二进制
    
    bytes=struct.pack('i',a)

    此时bytes就是一个string字符串,字符串按字节同a的二进制存储内容相同。

    再进行反操作

    现有二进制数据bytes,(其实就是字符串),将它反过来转换成python的数据类型:

    a,=struct.unpack('i',bytes)

    注意,unpack返回的是tuple

    所以如果只有一个变量的话:

    bytes=struct.pack('i',a)

    那么,解码的时候需要这样

    a,=struct.unpack('i',bytes) 或者 (a,)=struct.unpack('i',bytes)

    如果直接用a=struct.unpack('i',bytes),那么 a=(12,) ,是一个tuple而不是原来的浮点数了。
    如果是由多个数据构成的,可以这样:

    a='hello'
    
    b='world!'
    
    c=2
    
    d=45.123
    
    bytes=struct.pack('5s6sif',a,b,c,d)

    此时的bytes就是二进制形式的数据了,可以直接写入文件比如 binfile.write(bytes)

    然后,当我们需要时可以再读出来,bytes=binfile.read()

    再通过struct.unpack()解码成python变量

    a,b,c,d=struct.unpack('5s6sif',bytes)

    '5s6sif'这个叫做fmt,就是格式化字符串,由数字加字符构成,5s表示占5个字符的字符串,2i,表示2个整数等等,下面是可用的字符及类型,ctype表示可以与python中的类型一一对应。


    注意:二进制文件处理时会碰到的问题

    我们使用处理二进制文件时,需要用如下方法

    binfile=open(filepath,'rb')    读二进制文件

    binfile=open(filepath,'wb')    写二进制文件

    那么和binfile=open(filepath,'r')的结果到底有何不同呢?

    不同之处有两个地方:

    第一,使用'r'的时候如果碰到'0x1A',就会视为文件结束,这就是EOF。使用'rb'则不存在这个问题。即,如果你用二进制写入再用文本读出的话,如果其中存在'0X1A',就只会读出文件的一部分。使用'rb'的时候会一直读到文件末尾。

    第二,对于字符串x='abc def',我们可用len(x)得到它的长度为7, 我们称之为换行符,实际上是'0X0A'。当我们用'w'即文本方式写的时候,在windows平台上会自动将'0X0A'变成两个字符'0X0D','0X0A',即文件长度实际上变成8.。当用'r'文本方式读取时,又自动的转换成原来的换行符。如果换成'wb'二进制方式来写的话,则会保持一个字符不变,读取时也是原样读取。所以如果用文本方式写入,用二进制方式读取的话,就要考虑这多出的一个字节了。'0X0D'又称回车符。linux下不会变。因为linux只使用'0X0A'来表示换行。

    附加:

    import struct 
    
    # native byteorder 
    buffer = struct.pack("ihb", 1, 2, 3) 
    print repr(buffer) 
    print struct.unpack("ihb", buffer) 
    
    # data from a sequence, network byteorder 
    data = [1, 2, 3] 
    buffer = struct.pack("!ihb", *data)
    print repr(buffer) 
    print struct.unpack("!ihb", buffer)
    
    Output:
    
    'x01x00x00x00x02x00x03'
    (1, 2, 3)
    'x00x00x00x01x00x02x03'
    (1, 2, 3)

    首先将参数1,2,3打包,打包前1,2,3明显属于python数据类型中的integer,pack后就变成了C结构的二进制串,转成 python的string类型来显示就是  'x01x00x00x00x02x00x03'。由于本机是小端('little- endian',关于大端和小端的区别请参照这里,故而高位放在低地址段。i 代表C struct中的int类型,故而本机占4位,1则表示为01000000; h 代表C struct中的short类型,占2位,故表示为0200;同理b 代表C struct中的signed char类型,占1位,故而表示为03。

    其他结构的转换也类似,有些特别的可以参考Manual。

    在Format string 的首位,有一个可选字符来决定大端和小端,列表如下:

    Character Byte order Size Alignment
    @ native native native
    = native standard none
    < little-endian standard none
    > big-endian standard none
    ! network (= big-endian) standard none

    还有一个标准的选项,被描述为:如果使用标准的,则任何类型都无内存对齐。

    如果没有附加,默认为@,即使用本机的字符顺序(大端or小端),对于C结构的大小和内存中的对齐方式也是与本机相一致的(native),比如有的机器integer为2位而有的机器则为四位;有的机器内存对其位四位对齐,有的则是n位对齐(n未知,我也不知道多少)。

    比如刚才的小程序的后半部分,使用的format string中首位为!,即为大端模式标准对齐方式,故而输出的为'x00x00x00x01 x00x02 x03',其中高位自己就被放在内存的高地址位了。

    =======================================================================

    Python struct模块

    用处

    1. 按照指定格式将Python数据转换为字符串,该字符串为字节流,如网络传输时,不能传输int,此时先将int转化为字节流,然后再发送;
    2. 按照指定格式将字节流转换为Python指定的数据类型;
    3. 处理二进制数据,如果用struct来处理文件的话,需要用’wb’,’rb’以二进制(字节流)写,读的方式来处理文件;
    4. 处理c语言中的结构体;

    struct模块中的函数

    函数 return explain
    pack(fmt,v1,v2…) string 按照给定的格式(fmt),把数据转换成字符串(字节流),并将该字符串返回.
    pack_into(fmt,buffer,offset,v1,v2…) None 按照给定的格式(fmt),将数据转换成字符串(字节流),并将字节流写入以offset开始的buffer中.(buffer为可写的缓冲区,可用array模块)
    unpack(fmt,v1,v2…..) tuple 按照给定的格式(fmt)解析字节流,并返回解析结果
    pack_from(fmt,buffer,offset) tuple 按照给定的格式(fmt)解析以offset开始的缓冲区,并返回解析结果
    calcsize(fmt) size of fmt 计算给定的格式(fmt)占用多少字节的内存,注意对齐方式

    格式化字符串

    当打包或者解包的时,需要按照特定的方式来打包或者解包.该方式就是格式化字符串,它指定了数据类型,除此之外,还有用于控制字节顺序、大小和对齐方式的特殊字符.

    对齐方式

    为了同c中的结构体交换数据,还要考虑c或c++编译器使用了字节对齐,通常是以4个字节为单位的32位系统,故而struct根据本地机器字节顺序转换.可以用格式中的第一个字符来改变对齐方式.定义如下

    Character Byte order Size Alignment
    @(默认) 本机 本机 本机,凑够4字节
    = 本机 标准 none,按原字节数
    < 小端 标准 none,按原字节数
    > 大端 标准 none,按原字节数
    ! network(大端) 标准 none,按原字节数

    如果不懂大小端,见大小端参考网址.

    格式符

    格式符 C语言类型 Python类型 Standard size
    x pad byte(填充字节) no value  
    c char string of length 1 1
    b signed char integer 1
    B unsigned char integer 1
    ? _Bool bool 1
    h short integer 2
    H unsigned short integer 2
    i int integer 4
    I(大写的i) unsigned int integer 4
    l(小写的L) long integer 4
    L unsigned long long 4
    q long long long 8
    Q unsigned long long long 8
    f float float 4
    d double float 8
    s char[] string  
    p char[] string  
    P void * long  

    注- -!

    1. _Bool在C99中定义,如果没有这个类型,则将这个类型视为char,一个字节;
    2. q和Q只适用于64位机器;
    3. 每个格式前可以有一个数字,表示这个类型的个数,如s格式表示一定长度的字符串,4s表示长度为4的字符串;4i表示四个int;
    4. P用来转换一个指针,其长度和计算机相关;
    5. f和d的长度和计算机相关;

    code,使用示例

    #!/usr/bin/python
    # -*- coding:utf-8 -*-
    '''测试struct模块'''
    from struct import *
    import array
    
    def fun_calcsize():
        print 'ci:',calcsize('ci')#计算格式占内存大小
        print '@ci:',calcsize('@ci')
        print '=ci:',calcsize('=ci')
        print '>ci:',calcsize('>ci')
        print '<ci:',calcsize('<ci')
        print 'ic:',calcsize('ic')#计算格式占内存大小
        print '@ic:',calcsize('@ic')
        print '=ic:',calcsize('=ic')
        print '>ic:',calcsize('>ic')
        print '<ic:',calcsize('<ic')
    
    def fun_pack(Format,msg = [0x11223344,0x55667788]):
        result = pack(Format,*msg)
        print 'pack'.ljust(10),str(type(result)).ljust(20),
        for i in result:
            print hex(ord(i)), # ord把ASCII码表中的字符转换成对应的整形,hex将数值转化为十六进制
        print
    
        result = unpack(Format,result)
        print 'unpack'.ljust(10),str(type(result)).ljust(20),
        for i in result:
            print hex(i),
        print 
    
    def fun_pack_into(Format,msg = [0x11223344,0x55667788]):
        r = array.array('c',' '*8)#大小为8的可变缓冲区,writable buffer
        result = pack_into(Format,r,0,*msg)
        print 'pack_into'.ljust(10),str(type(result)).ljust(20),
        for i in r.tostring():
            print hex(ord(i)),
        print
    
        result = unpack_from(Format,r,0)
        print 'pack_from'.ljust(10),str(type(result)).ljust(20),
        for i in result:
            print hex(i),
        print
    
    def IsBig_Endian():
        '''判断本机为大/小端'''
        a = 0x12345678
        result = pack('i',a)#此时result就是一个string字符串,字符串按字节同a的二进制存储内容相同。
        if hex(ord(result[0])) == '0x78':
            print '本机为小端'
        else:
            print '本机为大端'
    
    def test():
        a = '1234'
        for i in a:
            print '字符%s的二进制:'%i,hex(ord(i))#字符对应ascii码表中对应整数的十六进制
    
        '''
        不用unpack()返回的数据也是可以使用pack()函数的,只要解包的字符串符合解包格式即可,
        pack()会按照解包格式将字符串在内存中的二进制重新解释(说的感觉不太好...,见下例)
        '''
        print '大端:',hex(unpack('>i',a)[0])#因为pack返回的是元组,即使只有一个元素也是元组的形式
        print '小端:',hex(unpack('<i',a)[0])
    
    
    if __name__ == "__main__":
        print '判断本机是否为大小端?',
        IsBig_Endian()
    
        fun_calcsize()
    
        print '大端:'
        Format = ">ii"
        fun_pack(Format)
        fun_pack_into(Format)
    
        print '小端:'
        Format = "<ii"
        fun_pack(Format)
        fun_pack_into(Format)
    
        print 'test'
        test()
        '''
        result:
        判断本机是否为大小端? 本机为小端
        ci: 8
        @ci: 8
        =ci: 5
        >ci: 5
        <ci: 5
        ic: 5
        @ic: 5
        =ic: 5
        >ic: 5
        <ic: 5
        大端:
        pack       <type 'str'>         0x11 0x22 0x33 0x44 0x55 0x66 0x77 0x88
        unpack     <type 'tuple'>       0x11223344 0x55667788
        pack_into  <type 'NoneType'>    0x11 0x22 0x33 0x44 0x55 0x66 0x77 0x88
        pack_from  <type 'tuple'>       0x11223344 0x55667788
        小端:
        pack       <type 'str'>         0x44 0x33 0x22 0x11 0x88 0x77 0x66 0x55
        unpack     <type 'tuple'>       0x11223344 0x55667788
        pack_into  <type 'NoneType'>    0x44 0x33 0x22 0x11 0x88 0x77 0x66 0x55
        pack_from  <type 'tuple'>       0x11223344 0x55667788
        test
        字符1的二进制: 0x31
        字符2的二进制: 0x32
        字符3的二进制: 0x33
        字符4的二进制: 0x34
        大端:0x31323334
        小端:0x34333231
        '''

     

    Python参考手册struct模块链接

    英文单词

    英文 中文
    compact 紧凑的,简洁的
    layout 布局
    pad bytes 填充字节
    alignment 对齐方式
    maintain 维持,保持
    proper 适当的
    correspond 一致,相应的
    platform-independent 平台依赖,即机器,操作系统不同,对齐方式,大小端等会有差异
    omit 忽略
    implicit 暗含的,隐式的
    native 本台电脑的
    presumably 假设
    even if 即使
    specify 指定
    mechanism 原理,机制,手法
    represented 表现,表示
    assumed 假定的,默认的

    关注公众号 海量干货等你
  • 相关阅读:
    wget 命令用法详解
    VI编辑器的使用方法
    Android APK反编译就这么简单 详解(附图)
    Nginx与X-Sendfile
    腾讯微博OAuth2.0认证介绍
    haporoxy的keeplaive ZZ
    如何监听非本地IP
    haproxy配置直接重定向url
    LVS与其他负载均衡软件的区别
    J2EE基础总结(4)——JSP
  • 原文地址:https://www.cnblogs.com/sowhat1412/p/12734352.html
Copyright © 2011-2022 走看看