整合營銷服務商

          電腦端+手機端+微信端=數(shù)據(jù)同步管理

          免費咨詢熱線:

          python struct 模塊

          python struct 模塊

          ython struct 模塊

          官方文檔:https://docs.python.org/3.7/library/struct.html?highlight=struct#module-struct

          該模塊執(zhí)行 Python 值和表示為 Python對象的 C struct 之間的轉換。bytes可用于處理存儲在文件中或來自網(wǎng)絡連接以及其他來源的二進制數(shù)據(jù)。它使用 格式字符串作為 C 結構布局的緊湊描述以及與 Python 值的預期轉換。

          默認情況下,打包給定 C 結構的結果包括填充字節(jié),以保持所涉及的 C 類型的正確對齊;同樣,開箱時也會考慮對齊。選擇此行為是為了使打包結構的字節(jié)與相應 C 結構的內(nèi)存布局完全對應。要處理與平臺無關的數(shù)據(jù)格式或省略隱式填充字節(jié),請使用standard大小和對齊而不是 native大小和對齊。

          struct 模塊的用途:

          1、按照指定格式將 Python 數(shù)據(jù)轉換為字符串,該字符串為字節(jié)流,如:網(wǎng)絡傳輸時不能傳輸int,此時先將int轉化為字節(jié)流,然后再發(fā)送。 2、按照指定格式將字節(jié)流轉換為 Python 指定的數(shù)據(jù)類型。 3、處理二進制數(shù)據(jù),如果用 struct 來處理圖片文件的話,需要使用 ‘rb’/‘wb’ 以二進制(字節(jié)流)讀寫的方式來處理文件。 4、處理 c 語言中的結構體。

          1.函數(shù)和異常

          該模塊定義了以下異常和函數(shù):

          • exception
          exception struct.error

          在各種場合提出異常;參數(shù)是一個描述錯誤的字符串。

          • pack
          struct.pack(format, v1, v2, ...) 

          返回一個字節(jié)對象,其中包含根據(jù)格式字符串格式打包的值 v1v2 、 ...。參數(shù)必須與格式要求的值完全匹配。

          • pack_into
          struct.pack_into(format, buffer, offset, v1, v2, ...)

          根據(jù)格式字符串格式打包值 v1,v2 ,...... ,并將打包的字節(jié)寫入從位置 offset 開始的可寫緩沖區(qū)。注意,偏移量是必需的參數(shù)。

          • unpack
          struct.unpack(format, buffer)

          根據(jù)格式字符串 format 從緩沖區(qū)中解包 。結果是一個元組,即使它只包含一個項目。緩沖區(qū)的字節(jié)大小必須與格式所需的大小相匹配。

          • unpack_from
          struct.unpack_from(format, buffer, offset=0)

          根據(jù)格式字符串,從位置偏移開始的緩沖區(qū)解包。結果是一個元組,即使它只包含一個項目。緩沖區(qū)的大小(以字節(jié)為單位)減去 offset 后,必須至少是格式所需的大小。

          • iter_unpack
          struct.iter_unpack(format, buffer)

          根據(jù)格式字符串 format從緩沖區(qū)中迭代解包。這個函數(shù)返回一個迭代器,它將從緩沖區(qū)中讀取相同大小的塊,直到它的所有內(nèi)容都被消耗完。緩沖區(qū)的字節(jié)大小必須是格式所需大小的倍數(shù)。每次迭代都會產(chǎn)生一個由格式字符串指定的元組。

          • calcsize
          struct.calcsize(format)

          返回與格式字符串 format 對應的結構體(以及由此產(chǎn)生的字節(jié)對象 )的大小。

          2.格式化字符串

          格式字符串是用于在打包和解包數(shù)據(jù)時指定預期布局的機制。它們是通過格式字符構建的,它指定了被打包/解包的數(shù)據(jù)類型。此外,還有用于控制字節(jié)順序、大小和對齊的特殊字符。

          2.1 字節(jié)順序、大小和對齊

          默認情況下,C 類型以機器的本機格式和字節(jié)順序表示,并在必要時通過跳過填充字節(jié)來正確對齊(根據(jù) C 編譯器使用的規(guī)則)。或者,格式字符串的第一個字符可用于指示打包數(shù)據(jù)的字節(jié)順序、大小和對齊方式,如下表所示:

          Character

          Byte order

          Size

          Alignment

          @

          native

          native

          native

          =

          native

          standard

          none

          <

          little-endian

          standard

          none

          >

          big-endian

          standard

          none

          !

          network (=big-endian)

          standard

          none

          如果第一個字符不是其中之一,'@'則為默認。

          本機字節(jié)順序是大端或小端,具體取決于主機系統(tǒng)。例如:

          • Intel x86 和 AMD64 (x86-64) 是 little-endian;
          • 摩托羅拉 68000 和 PowerPC G5 是大端;
          • ARM 和 Intel Itanium 具有可切換的字節(jié)序(雙字節(jié)序)。

          可以使用sys.byteorder檢查系統(tǒng)的字節(jié)順序。

          本機大小和對齊方式是使用 C 編譯器的 sizeof 表達式確定的。這總是與本機字節(jié)順序相結合。

          標準大小僅取決于格式字符;

          '@''='之間的區(qū)別:兩者都使用本機字節(jié)順序,但后者的大小和對齊方式是標準化的。

          '!'適用于那些聲稱他們不記得網(wǎng)絡字節(jié)順序是大端還是小端的人。

          無法指示非本機字節(jié)順序(強制字節(jié)交換);使用適當?shù)?<>

          注意:

          1. 填充僅在連續(xù)的結構成員之間自動添加。在編碼結構的開頭或結尾處不添加任何填充。
          2. 使用非原生大小和對齊方式時不添加填充,例如使用“<”、“>”、“=”和“!”。
          3. 要將結構的結尾與特定類型的對齊要求對齊,請以該類型的代碼結束格式,并且重復計數(shù)為零。

          2.2格式字符串

          格式字符具有以下含義;考慮到它們的類型,C 和 Python 值之間的轉換應該是顯而易見的。“標準大小”列是指使用標準大小時打包值的大小(以字節(jié)為單位);也就是說,當格式字符串以 '<', '>', '!''=' 中的一個開頭時。

          Format

          C Type

          Python type

          Standard size

          x

          pad byte

          no value


          c

          char

          bytes 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

          4

          l

          long

          integer

          4

          L

          unsigned long

          integer

          4

          q

          long long

          integer

          8

          Q

          unsigned long long

          integer

          8

          n

          ssize_t

          integer


          N

          size_t

          integer


          e

          (6)

          float

          2

          f

          float

          float

          4

          d

          double

          float

          8

          s

          char[]

          bytes


          p

          char[]

          bytes


          P

          void *

          integer


          格式字符前面可以有一個整數(shù)重復計數(shù)。例如,格式字符串'4h'的含義與'hhhh'

          格式之間的空白字符被忽略;計數(shù)及其格式不能包含空格。

          對于's'格式字符,計數(shù)被解釋為字節(jié)的長度,而不是像其他格式字符那樣的重復計數(shù);例如, '10s'表示單個 10 字節(jié)字符串,而'10c'表示 10 個字符。如果未給出計數(shù),則默認為 1。對于打包,字符串將被截斷或用空字節(jié)填充以使其適合。對于解包,生成的字節(jié)對象始終具有完全指定的字節(jié)數(shù)。作為一種特殊情況,'0s'表示單個空字符串(同時 '0c'表示 0 個字符)。

          x當使用其中一種整數(shù)格式('b', 'B', 'h', 'H', 'i', 'I', 'l', 'L', 'q', 'Q')打包值時,如果x超出該格式的有效范圍,則引發(fā) struct.error。

          格式字符對'p'“Pascal 字符串”進行編碼,這意味著存儲在固定字節(jié)數(shù)中的短可變長度字符串,由計數(shù)給出。存儲的第一個字節(jié)是字符串的長度,或 255,以較小者為準。字符串的字節(jié)如下:如果傳入的字符串pack()太長(長于 count 減 1),則只 count-1 存儲字符串的前導字節(jié)。如果字符串短于 count-1,則用空字節(jié)填充它,以便使用精確計數(shù)的字節(jié)。請注意,對于unpack()'p'格式字符會消耗 count字節(jié),但返回的字符串不能包含超過 255 個字節(jié)。

          對于'?'格式字符,返回值為TrueFalse。打包時使用參數(shù)對象的真值。本機或標準布爾表示中的 0 或 1 將被打包,并且任何非零值將 在解包時為 True

          3.示例

          所有示例都假定本機字節(jié)順序、大小和與大端機器對齊。

          打包/解包三個整數(shù)的基本示例:

          >>> from struct import *
          >>> pack('hhl', 1, 2, 3)
          b'\x00\x01\x00\x02\x00\x00\x00\x03'
          >>> unpack('hhl', b'\x00\x01\x00\x02\x00\x00\x00\x03')
          (1, 2, 3)
          >>> calcsize('hhl')
          8

          解壓的字段可以通過將它們分配給變量或?qū)⒔Y果包裝在命名元組中來命名:

          >>> record=b'raymond   \x32\x12\x08\x01\x08'
          >>> name, serialnum, school, gradelevel=unpack('<10sHHb', record)
          
          >>> from collections import namedtuple
          >>> Student=namedtuple('Student', 'name serialnum school gradelevel')
          >>> Student._make(unpack('<10sHHb', record))
          Student(name=b'raymond   ', serialnum=4658, school=264, gradelevel=8)

          格式字符的順序可能會影響大小,因為滿足對齊要求所需的填充是不同的:

          >>> pack('ci', b'*', 0x12131415)
          b'*\x00\x00\x00\x12\x13\x14\x15'
          >>> pack('ic', 0x12131415, b'*')
          b'\x12\x13\x14\x15*'
          >>> calcsize('ci')
          8
          >>> calcsize('ic')
          5

          以下格式'llh0l'在末尾指定兩個填充字節(jié),假設 long 在 4 字節(jié)邊界上對齊:

          >>> pack('llh0l', 1, 2, 3)
          b'\x00\x00\x00\x01\x00\x00\x00\x02\x00\x03\x00\x00'

          4.類 Struct

          該模塊還定義了以下類型:

          class struct.Struct(format)

          返回一個新的 Struct 對象,該對象根據(jù)格式字符串 format 寫入和讀取二進制數(shù)據(jù)。一次創(chuàng)建一個 Struct 對象并調(diào)用它的方法比調(diào)用 struct 具有相同格式的函數(shù)更有效,因為格式字符串只需要編譯一次。

          編譯后的Struct對象支持以下方法和屬性:

          • pack
          pack(v1, v2, ...)

          與函數(shù) pack() 相同,使用編譯格式。

          • pack_into
          pack_into(buffer, offset, v1, v2, ...)

          與函數(shù) pack_into() 相同,使用編譯格式 。

          • unpack
          unpack(buffer)

          與函數(shù) unpack() 相同,使用編譯格式。

          • unpack_from
          unpack_from(buffer, offset=0)

          與函數(shù) unpack_from() 相同,使用編譯格式。

          • iter_unpack
          iter_unpack(buffer)

          與函數(shù) iter_unpack() 相同,使用編譯格式。

          • format
          format

          用于構造此 Struct 對象的格式字符串。

          • size
          size

          對應于 format 的結構體(以及由此 pack() 方法產(chǎn)生的字節(jié)對象)的大小。

          struct是python(包括版本2和3)中的內(nèi)建模塊,它用來在c語言中的結構體與python中的字符串之間進行轉換,數(shù)據(jù)一般來自文件或者網(wǎng)絡。

          常用方法

          struct.pack(fmt,v1,v2,…)

          返回的是一個字符串,是參數(shù)按照fmt數(shù)據(jù)格式組合而成。

          struct.unpack(fmt,string)

          按照給定數(shù)據(jù)格式解開(通常都是由struct.pack進行打包)數(shù)據(jù),返回值是一個tuple

          格式符

          下面2張表來自官網(wǎng)

          Character

          Byte order

          Size

          Alignment

          @

          native

          native

          native

          =

          native

          standard

          none

          <

          little-endian

          standard

          none

          >

          big-endian

          standard

          none

          !

          network (=big-endian)

          standard

          none

          Format

          C Type

          Python type

          Standard size

          Notes

          x

          pad byte

          no value



          c

          char

          string of length 1

          1


          b

          signed char

          integer

          1

          (3)

          B

          unsigned char

          integer

          1

          (3)

          ?

          _Bool

          bool

          1

          (1)

          h

          short

          integer

          2

          (3)

          H

          unsigned short

          integer

          2

          (3)

          i

          int

          integer

          4

          (3)

          I

          unsigned int

          integer

          4

          (3)

          l

          long

          integer

          4

          (3)

          L

          unsigned long

          integer

          4

          (3)

          q

          long long

          integer

          8

          (2), (3)

          Q

          unsigned long long

          integer

          8

          (2), (3)

          f

          float

          float

          4

          (4)

          d

          double

          float

          8

          (4)

          s

          char[]

          string



          p

          char[]

          string



          P

          void *

          integer


          (5), (3)

          實例

          理論性的東西看起來都比較枯燥,來個實例代碼就容易理解多了。本例來實現(xiàn)往一個2進制文件中按照某種特定格式寫入數(shù)據(jù),之后再將它讀出。相信通過這個例子,你就能基本掌握struct的使用。

          # -*- coding: utf-8 -*-
          
          '''
          數(shù)據(jù)格式為
          姓名 年齡 性別   職業(yè)
          lily 18  female teacher
          '''
          
          import os
          import struct
          
          fp=open('test.bin','wb')
          
          # 按照上面的格式將數(shù)據(jù)寫入文件中
          # 這里如果string類型的話,在pack函數(shù)中就需要encode('utf-8')
          name=b'lily'
          age=18
          sex=b'female'
          job=b'teacher'
          
          # int類型占4個字節(jié)
          fp.write(struct.pack('4si6s7s', name,age,sex,job))
          fp.flush()
          fp.close()
          
          # 將文件中寫入的數(shù)據(jù)按照格式讀取出來
          fd=open('test.bin','rb')
          # 21=4 + 4 + 6 + 7
          print(struct.unpack('4si6s7s',fd.read(21)))
          fd.close()
          

          運行上面的代碼,可以看到讀出的數(shù)據(jù)與寫入的數(shù)據(jù)是完全一致的。

          python test.py
          (b'lily', 18, b'female', b'teacher')
          
          Process finished with exit code 0
          

          參考資料

          • https://docs.python.org/2/library/struct.html

          近項目中遇到一個文檔解析的場景,目標是在瀏覽器端能預覽markdown文件。

          拿到這個需求,相信很多前端同學會想到使用開源的庫,比如github上很受歡迎的marked,當然,是一個簡單而有效的方案。

          但是如果你了解webassembly一點點的話,相信你也會覺得,像這種數(shù)據(jù)處理的活交給C++來干,沒錯。

          好吧,我們抱著這個猜想開始下面的嘗試吧。

          搭建環(huán)境

          為了把C++代碼編譯成能在瀏覽器上運行的wasm,我們需要使用 Emscripten。 安裝Emscripten依賴如下幾個工具:Git、CMake、GCC、Python 2.7.x。

          編譯 Emscripten:

          git clone https://github.com/juj/emsdk.git
          cd emsdk
          ./emsdk install sdk-incoming-64bit binaryen-master-64bit
          ./emsdk activate sdk-incoming-64bit binaryen-master-64bit
          source ./emsdk_env.sh
          

          創(chuàng)建項目

          推薦如下的目錄結構:

          .
          ├── build
          ├── build.sh
          ├── include
          │   └── sundown
          │       ├── autolink.c
          │       ├── autolink.h
          │       ├── buffer.c
          │       ├── buffer.h
          │       ├── houdini.h
          │       ├── houdini_href_e.c
          │       ├── houdini_html_e.c
          │       ├── html.c
          │       ├── html.h
          │       ├── html_blocks.h
          │       ├── markdown.c
          │       ├── markdown.h
          │       ├── stack.c
          │       └── stack.h
          ├── src
          │   ├── index.cc
          │   └── wasm.c
          └── web
              ├── index.html
              ├── index.js
              ├── index.wasm
              └── test.md
          

          這里為了測試,我是直接使用了通過C解析markdown文檔開源庫sundown。就是目錄中的include/sundown。

          我們需要一個入口文件,取一個名字wasm.c。

          #include <errno.h>
          #include <stdio.h>
          #include <stdlib.h>
          #include <string.h>
          #include <emscripten/emscripten.h>
          
          #include "markdown.h"
          #include "html.h"
          #include "buffer.h"
          
          #define READ_UNIT 1024
          #define OUTPUT_UNIT 64
          
          const char*
          EMSCRIPTEN_KEEPALIVE wasm_markdown(char* source)
          {
              struct buf *ib, *ob;
              struct sd_callbacks callbacks;
              struct html_renderopt options;
              struct sd_markdown *markdown;
          
              ib=bufnew(READ_UNIT);
              bufgrow(ib, READ_UNIT);
              size_t char_len=strlen(source);
              bufput(ib, source, char_len);
          
              ob=bufnew(OUTPUT_UNIT);
              sdhtml_renderer(&callbacks, &options, 0);
              markdown=sd_markdown_new(0, 16, &callbacks, &options);
              sd_markdown_render(ob, ib->data, ib->size, markdown);
              sd_markdown_free(markdown);
          
              /* cleanup */
              bufrelease(ib);
              bufrelease(ob);
          
              return (char *)(ob->data);
          }
          

          入口文件是調(diào)用lib的方法實現(xiàn)md字符解析,輸出html格式的字符。完成編碼部分,接下來就可以構建了。

          這是我的build腳本:

          emcc src/wasm.c \
          -O3 \
          ./include/sundown/markdown.c \
          ./include/sundown/buffer.c \
          ./include/sundown/autolink.c \
          ./include/sundown/html.c \
          ./include/sundown/houdini_href_e.c \
          ./include/sundown/houdini_html_e.c \
          ./include/sundown/stack.c \
          -s EXTRA_EXPORTED_RUNTIME_METHODS='["cwrap", "ccall"]' \
          -s TOTAL_MEMORY=67108864 \
          -s TOTAL_STACK=31457280 \
          -o build/index.js -I./include/sundown \
          
          cp build/index.js build/index.wasm web/
          

          解釋下其中的幾個參數(shù):

          • EXTRA_EXPORTED_RUNTIME_METHODS,允許js通過cwrap和ccall的方式調(diào)用c函數(shù)。
          • TOTAL_MEMORY,分配內(nèi)存大小。TOTAL_MEMORY,分配棧大小。因為md文檔比較大,默認的5M不夠用。

          測試

          啟動一個web Server,因為webAssembly不支持file協(xié)議下加載。

          emrun --port 3000 ./web
          

          emrun是Emscriptem自帶的webServer工具,你也可以使用你喜歡的。

          初始化并調(diào)用C接口。

          <script src="http://127.0.0.1:3000/markdown.js"></script>
          <script>
          const wasm_markdown=Module.cwrap('wasm_markdown', 'string', ['string']);
          console.log(wasm_markdown('# hello wasm'));
          // 輸出:<h1>hello wasm</h1>
          </script>
          

          多線程

          先看DEMO,分析在代碼之后。

          const mdUrl='http://127.0.0.1:3000/markdown.js';
          
          class MarkdownParse {
              isInited=false;
              worker=undefined;
              async init(url) {
                  if (this.isInited) {
                      return;
                  }
                  return new Promise(rs=> {
                      const workerScripts=`
                          addEventListener('message', async(e)=> {
                            if (e.data=="startWorker") {
                              importScripts("${url}");
                              postMessage({ type: 'init' });
                            } else if (e.data.type==='parseData') {
                            	await markdown.ready;
                            	const data=markdown.parse(e.data.input);
                            	postMessage({ type: 'parseSuccess', data }); 
                            }
                          }, false)`;
                      this.worker=new Worker(window.URL.createObjectURL(new Blob([workerScripts])));
                      this.worker.addEventListener('message', e=> e.data.type==='init' ? rs() : '');
                      this.worker.postMessage("startWorker");
                      this.isInited=true;
                  })
              }
              async parse(input) {
                  if (!this.isInited) {
                      await this.init(mdUrl);
                  }
                  return new Promise(resolve=> {
                      this.worker.addEventListener('message', 
                          e=> e.data.type==='parseSuccess' ? 
                              resolve(e.data.data) : null
                      );
                      this.worker.postMessage({ type: 'parseData', input });
                  });
              }
          };
          
          (async()=> {
              const md=new MarkdownParse();
              // // 觸發(fā)多次解析
              // const html=[
              // 	await md.parse('# Hello Markdown'),	
              // 	await md.parse('- [ ] Todo1'),
              // 	await md.parse('- [ ] Todo2'),
              // 	await md.parse('- [x] Todo3'),
              // 	await md.parse('> Date.now()'),
              // 	await md.parse('`const a=Date.now();`'),
              // ];
              // document.querySelector('#markdown-body').innerHTML=html.join('');
          
              md.parse('123');
              const text=await (await fetch('test.md')).text();
              const testJS=()=> {
                  const a=Date.now();
                  // marked 是JS版本的markdown解析庫
                  marked(text);
                  return Date.now() - a;
              };
              const testWasm=async()=> {
                  const a=Date.now();
                  await md.parse(text);
                  return Date.now() - a;
              };
              const vs=async()=> {
                  const result={
                      js_parse_time: testJS(),
                      wasm_parse_time: await testWasm(),
                  };
                  result.speed=result.js_parse_time / result.wasm_parse_time;
                  // 顯示wasm和JS的解析速度對比
                  document.querySelector('#markdown-body').innerHTML=JSON.stringify(result);
              }
              await vs();
              // 輸出markdown的HTML
              // document.querySelector('#markdown-body').innerHTML=await md.parse(text);
          
          })();
          

          解析下思路,線抽線一個類 MarkdownParse 來實現(xiàn)wasm的加載和初始化以及api。 默認情況下, web worker是不允許跨域的,但是,有方案的。web worker內(nèi)部提供了一個importScripts方法來加載非同源的JS。

          收獲&總結

          到此我們完成了今天的構建webassembly應用實例,有如下收獲:

          • wasm比JS,效率高出3倍左右
          • 通過web worker去處理wasm的加載,初始化,數(shù)據(jù)計算處理等,不會占用瀏覽器的主線程

          總結,本文可能只是一個很小的場景,而且單從效率這點來看,JS的200ms對比wasm的50ms,其實對于前端來說,并沒有特別驚艷的優(yōu)勢。BUT,這只是一個開始,wasm對前端帶來的性能提升會百花齊放,我們拭目以待吧~


          主站蜘蛛池模板: 精品一区二区久久| 亚洲AV无码一区二三区| 色窝窝无码一区二区三区色欲| 免费在线观看一区| 国产成人精品一区二三区在线观看 | 后入内射国产一区二区| 男人的天堂亚洲一区二区三区 | 精品日本一区二区三区在线观看| 天天看高清无码一区二区三区| 国模大尺度视频一区二区| 中文字幕一区二区精品区| 久久中文字幕无码一区二区| 无码精品久久一区二区三区| 亚洲爆乳精品无码一区二区三区 | 国产怡春院无码一区二区| 精品一区二区三区无码视频| 久久精品无码一区二区三区日韩 | 国产在线精品一区二区中文| 中文字幕av一区| 亚洲日本一区二区一本一道| 久久久国产精品亚洲一区| 日韩精品一区二区三区在线观看 | 亚洲A∨精品一区二区三区| 色久综合网精品一区二区| 国产成人高清亚洲一区久久| 国模吧一区二区三区| 日韩三级一区二区| 99久久综合狠狠综合久久一区| 国产肥熟女视频一区二区三区 | 在线观看一区二区三区视频| 亚洲色精品vr一区二区三区| 日韩免费视频一区| 亚洲一区精品视频在线| 亚洲av区一区二区三| 欧美一区内射最近更新| 久久人妻av一区二区软件| 无码精品前田一区二区| 无码一区二区三区免费视频| 文中字幕一区二区三区视频播放| 无码人妻一区二区三区免费视频| 国产一区二区三区影院|