Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
官方文檔:https://docs.python.org/3.7/library/struct.html?highlight=struct#module-struct
該模塊執(zhí)行 Python 值和表示為 Python對(duì)象的 C struct 之間的轉(zhuǎn)換。bytes可用于處理存儲(chǔ)在文件中或來(lái)自網(wǎng)絡(luò)連接以及其他來(lái)源的二進(jìn)制數(shù)據(jù)。它使用 格式字符串作為 C 結(jié)構(gòu)布局的緊湊描述以及與 Python 值的預(yù)期轉(zhuǎn)換。
默認(rèn)情況下,打包給定 C 結(jié)構(gòu)的結(jié)果包括填充字節(jié),以保持所涉及的 C 類型的正確對(duì)齊;同樣,開(kāi)箱時(shí)也會(huì)考慮對(duì)齊。選擇此行為是為了使打包結(jié)構(gòu)的字節(jié)與相應(yīng) C 結(jié)構(gòu)的內(nèi)存布局完全對(duì)應(yīng)。要處理與平臺(tái)無(wú)關(guān)的數(shù)據(jù)格式或省略隱式填充字節(jié),請(qǐng)使用standard大小和對(duì)齊而不是 native大小和對(duì)齊。
struct 模塊的用途:
1、按照指定格式將 Python 數(shù)據(jù)轉(zhuǎn)換為字符串,該字符串為字節(jié)流,如:網(wǎng)絡(luò)傳輸時(shí)不能傳輸int,此時(shí)先將int轉(zhuǎn)化為字節(jié)流,然后再發(fā)送。 2、按照指定格式將字節(jié)流轉(zhuǎn)換為 Python 指定的數(shù)據(jù)類型。 3、處理二進(jìn)制數(shù)據(jù),如果用 struct 來(lái)處理圖片文件的話,需要使用 ‘rb’/‘wb’ 以二進(jìn)制(字節(jié)流)讀寫的方式來(lái)處理文件。 4、處理 c 語(yǔ)言中的結(jié)構(gòu)體。
該模塊定義了以下異常和函數(shù):
exception struct.error
在各種場(chǎng)合提出異常;參數(shù)是一個(gè)描述錯(cuò)誤的字符串。
struct.pack(format, v1, v2, ...)
返回一個(gè)字節(jié)對(duì)象,其中包含根據(jù)格式字符串格式打包的值 v1、v2 、 ...。參數(shù)必須與格式要求的值完全匹配。
struct.pack_into(format, buffer, offset, v1, v2, ...)
根據(jù)格式字符串格式打包值 v1,v2 ,...... ,并將打包的字節(jié)寫入從位置 offset 開(kāi)始的可寫緩沖區(qū)。注意,偏移量是必需的參數(shù)。
struct.unpack(format, buffer)
根據(jù)格式字符串 format 從緩沖區(qū)中解包 。結(jié)果是一個(gè)元組,即使它只包含一個(gè)項(xiàng)目。緩沖區(qū)的字節(jié)大小必須與格式所需的大小相匹配。
struct.unpack_from(format, buffer, offset=0)
根據(jù)格式字符串,從位置偏移開(kāi)始的緩沖區(qū)解包。結(jié)果是一個(gè)元組,即使它只包含一個(gè)項(xiàng)目。緩沖區(qū)的大小(以字節(jié)為單位)減去 offset 后,必須至少是格式所需的大小。
struct.iter_unpack(format, buffer)
根據(jù)格式字符串 format從緩沖區(qū)中迭代解包。這個(gè)函數(shù)返回一個(gè)迭代器,它將從緩沖區(qū)中讀取相同大小的塊,直到它的所有內(nèi)容都被消耗完。緩沖區(qū)的字節(jié)大小必須是格式所需大小的倍數(shù)。每次迭代都會(huì)產(chǎn)生一個(gè)由格式字符串指定的元組。
struct.calcsize(format)
返回與格式字符串 format 對(duì)應(yīng)的結(jié)構(gòu)體(以及由此產(chǎn)生的字節(jié)對(duì)象 )的大小。
格式字符串是用于在打包和解包數(shù)據(jù)時(shí)指定預(yù)期布局的機(jī)制。它們是通過(guò)格式字符構(gòu)建的,它指定了被打包/解包的數(shù)據(jù)類型。此外,還有用于控制字節(jié)順序、大小和對(duì)齊的特殊字符。
默認(rèn)情況下,C 類型以機(jī)器的本機(jī)格式和字節(jié)順序表示,并在必要時(shí)通過(guò)跳過(guò)填充字節(jié)來(lái)正確對(duì)齊(根據(jù) C 編譯器使用的規(guī)則)。或者,格式字符串的第一個(gè)字符可用于指示打包數(shù)據(jù)的字節(jié)順序、大小和對(duì)齊方式,如下表所示:
Character | Byte order | Size | Alignment |
@ | native | native | native |
= | native | standard | none |
< | little-endian | standard | none |
> | big-endian | standard | none |
! | network (=big-endian) | standard | none |
如果第一個(gè)字符不是其中之一,'@'則為默認(rèn)。
本機(jī)字節(jié)順序是大端或小端,具體取決于主機(jī)系統(tǒng)。例如:
可以使用sys.byteorder檢查系統(tǒng)的字節(jié)順序。
本機(jī)大小和對(duì)齊方式是使用 C 編譯器的 sizeof 表達(dá)式確定的。這總是與本機(jī)字節(jié)順序相結(jié)合。
標(biāo)準(zhǔn)大小僅取決于格式字符;
'@'和'='之間的區(qū)別:兩者都使用本機(jī)字節(jié)順序,但后者的大小和對(duì)齊方式是標(biāo)準(zhǔn)化的。
'!'適用于那些聲稱他們不記得網(wǎng)絡(luò)字節(jié)順序是大端還是小端的人。
無(wú)法指示非本機(jī)字節(jié)順序(強(qiáng)制字節(jié)交換);使用適當(dāng)?shù)?< 或 > 。
注意:
格式字符具有以下含義;考慮到它們的類型,C 和 Python 值之間的轉(zhuǎn)換應(yīng)該是顯而易見(jiàn)的。“標(biāo)準(zhǔn)大小”列是指使用標(biāo)準(zhǔn)大小時(shí)打包值的大小(以字節(jié)為單位);也就是說(shuō),當(dāng)格式字符串以 '<', '>', '!' 或 '=' 中的一個(gè)開(kāi)頭時(shí)。
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 |
格式字符前面可以有一個(gè)整數(shù)重復(fù)計(jì)數(shù)。例如,格式字符串'4h'的含義與'hhhh'。
格式之間的空白字符被忽略;計(jì)數(shù)及其格式不能包含空格。
對(duì)于's'格式字符,計(jì)數(shù)被解釋為字節(jié)的長(zhǎng)度,而不是像其他格式字符那樣的重復(fù)計(jì)數(shù);例如, '10s'表示單個(gè) 10 字節(jié)字符串,而'10c'表示 10 個(gè)字符。如果未給出計(jì)數(shù),則默認(rèn)為 1。對(duì)于打包,字符串將被截?cái)嗷蛴每兆止?jié)填充以使其適合。對(duì)于解包,生成的字節(jié)對(duì)象始終具有完全指定的字節(jié)數(shù)。作為一種特殊情況,'0s'表示單個(gè)空字符串(同時(shí) '0c'表示 0 個(gè)字符)。
x當(dāng)使用其中一種整數(shù)格式('b', 'B', 'h', 'H', 'i', 'I', 'l', 'L', 'q', 'Q')打包值時(shí),如果x超出該格式的有效范圍,則引發(fā) struct.error。
格式字符對(duì)'p'“Pascal 字符串”進(jìn)行編碼,這意味著存儲(chǔ)在固定字節(jié)數(shù)中的短可變長(zhǎng)度字符串,由計(jì)數(shù)給出。存儲(chǔ)的第一個(gè)字節(jié)是字符串的長(zhǎng)度,或 255,以較小者為準(zhǔn)。字符串的字節(jié)如下:如果傳入的字符串pack()太長(zhǎng)(長(zhǎng)于 count 減 1),則只 count-1 存儲(chǔ)字符串的前導(dǎo)字節(jié)。如果字符串短于 count-1,則用空字節(jié)填充它,以便使用精確計(jì)數(shù)的字節(jié)。請(qǐng)注意,對(duì)于unpack(),'p'格式字符會(huì)消耗 count字節(jié),但返回的字符串不能包含超過(guò) 255 個(gè)字節(jié)。
對(duì)于'?'格式字符,返回值為True或 False。打包時(shí)使用參數(shù)對(duì)象的真值。本機(jī)或標(biāo)準(zhǔn)布爾表示中的 0 或 1 將被打包,并且任何非零值將 在解包時(shí)為 True。
所有示例都假定本機(jī)字節(jié)順序、大小和與大端機(jī)器對(duì)齊。
打包/解包三個(gè)整數(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
解壓的字段可以通過(guò)將它們分配給變量或?qū)⒔Y(jié)果包裝在命名元組中來(lái)命名:
>>> 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)
格式字符的順序可能會(huì)影響大小,因?yàn)闈M足對(duì)齊要求所需的填充是不同的:
>>> 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'在末尾指定兩個(gè)填充字節(jié),假設(shè) long 在 4 字節(jié)邊界上對(duì)齊:
>>> pack('llh0l', 1, 2, 3)
b'\x00\x00\x00\x01\x00\x00\x00\x02\x00\x03\x00\x00'
該模塊還定義了以下類型:
class struct.Struct(format)
返回一個(gè)新的 Struct 對(duì)象,該對(duì)象根據(jù)格式字符串 format 寫入和讀取二進(jìn)制數(shù)據(jù)。一次創(chuàng)建一個(gè) Struct 對(duì)象并調(diào)用它的方法比調(diào)用 struct 具有相同格式的函數(shù)更有效,因?yàn)楦袷阶址恍枰幾g一次。
編譯后的Struct對(duì)象支持以下方法和屬性:
pack(v1, v2, ...)
與函數(shù) pack() 相同,使用編譯格式。
pack_into(buffer, offset, v1, v2, ...)
與函數(shù) pack_into() 相同,使用編譯格式 。
unpack(buffer)
與函數(shù) unpack() 相同,使用編譯格式。
unpack_from(buffer, offset=0)
與函數(shù) unpack_from() 相同,使用編譯格式。
iter_unpack(buffer)
與函數(shù) iter_unpack() 相同,使用編譯格式。
format
用于構(gòu)造此 Struct 對(duì)象的格式字符串。
size
對(duì)應(yīng)于 format 的結(jié)構(gòu)體(以及由此 pack() 方法產(chǎn)生的字節(jié)對(duì)象)的大小。
struct是python(包括版本2和3)中的內(nèi)建模塊,它用來(lái)在c語(yǔ)言中的結(jié)構(gòu)體與python中的字符串之間進(jìn)行轉(zhuǎn)換,數(shù)據(jù)一般來(lái)自文件或者網(wǎng)絡(luò)。
返回的是一個(gè)字符串,是參數(shù)按照f(shuō)mt數(shù)據(jù)格式組合而成。
按照給定數(shù)據(jù)格式解開(kāi)(通常都是由struct.pack進(jìn)行打包)數(shù)據(jù),返回值是一個(gè)tuple
下面2張表來(lái)自官網(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) |
理論性的東西看起來(lái)都比較枯燥,來(lái)個(gè)實(shí)例代碼就容易理解多了。本例來(lái)實(shí)現(xiàn)往一個(gè)2進(jìn)制文件中按照某種特定格式寫入數(shù)據(jù),之后再將它讀出。相信通過(guò)這個(gè)例子,你就能基本掌握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個(gè)字節(jié)
fp.write(struct.pack('4si6s7s', name,age,sex,job))
fp.flush()
fp.close()
# 將文件中寫入的數(shù)據(jù)按照格式讀取出來(lái)
fd=open('test.bin','rb')
# 21=4 + 4 + 6 + 7
print(struct.unpack('4si6s7s',fd.read(21)))
fd.close()
運(yùn)行上面的代碼,可以看到讀出的數(shù)據(jù)與寫入的數(shù)據(jù)是完全一致的。
python test.py
(b'lily', 18, b'female', b'teacher')
Process finished with exit code 0
近項(xiàng)目中遇到一個(gè)文檔解析的場(chǎng)景,目標(biāo)是在瀏覽器端能預(yù)覽markdown文件。
拿到這個(gè)需求,相信很多前端同學(xué)會(huì)想到使用開(kāi)源的庫(kù),比如github上很受歡迎的marked,當(dāng)然,是一個(gè)簡(jiǎn)單而有效的方案。
但是如果你了解webassembly一點(diǎn)點(diǎn)的話,相信你也會(huì)覺(jué)得,像這種數(shù)據(jù)處理的活交給C++來(lái)干,沒(méi)錯(cuò)。
好吧,我們抱著這個(gè)猜想開(kāi)始下面的嘗試吧。
為了把C++代碼編譯成能在瀏覽器上運(yùn)行的wasm,我們需要使用 Emscripten。 安裝Emscripten依賴如下幾個(gè)工具: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
推薦如下的目錄結(jié)構(gòu):
.
├── 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è)試,我是直接使用了通過(guò)C解析markdown文檔開(kāi)源庫(kù)sundown。就是目錄中的include/sundown。
我們需要一個(gè)入口文件,取一個(gè)名字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的方法實(shí)現(xiàn)md字符解析,輸出html格式的字符。完成編碼部分,接下來(lái)就可以構(gòu)建了。
這是我的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/
解釋下其中的幾個(gè)參數(shù):
啟動(dòng)一個(gè)web Server,因?yàn)閣ebAssembly不支持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解析庫(kù)
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的解析速度對(duì)比
document.querySelector('#markdown-body').innerHTML=JSON.stringify(result);
}
await vs();
// 輸出markdown的HTML
// document.querySelector('#markdown-body').innerHTML=await md.parse(text);
})();
解析下思路,線抽線一個(gè)類 MarkdownParse 來(lái)實(shí)現(xiàn)wasm的加載和初始化以及api。 默認(rèn)情況下, web worker是不允許跨域的,但是,有方案的。web worker內(nèi)部提供了一個(gè)importScripts方法來(lái)加載非同源的JS。
到此我們完成了今天的構(gòu)建webassembly應(yīng)用實(shí)例,有如下收獲:
總結(jié),本文可能只是一個(gè)很小的場(chǎng)景,而且單從效率這點(diǎn)來(lái)看,JS的200ms對(duì)比wasm的50ms,其實(shí)對(duì)于前端來(lái)說(shuō),并沒(méi)有特別驚艷的優(yōu)勢(shì)。BUT,這只是一個(gè)開(kāi)始,wasm對(duì)前端帶來(lái)的性能提升會(huì)百花齊放,我們拭目以待吧~
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。