obstruct 0.2.2__tar.gz → 0.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. obstruct-0.2.4/.idea/modules.xml +8 -0
  2. obstruct-0.2.2/.idea/Hydrogenlib-Objective-Struct.iml → obstruct-0.2.4/.idea/obstruct.iml +1 -4
  3. {obstruct-0.2.2 → obstruct-0.2.4}/.idea/workspace.xml +49 -21
  4. {obstruct-0.2.2 → obstruct-0.2.4}/PKG-INFO +32 -9
  5. {obstruct-0.2.2 → obstruct-0.2.4}/README.md +31 -8
  6. obstruct-0.2.4/src/obstruct/__version__.py +1 -0
  7. {obstruct-0.2.2 → obstruct-0.2.4}/src/obstruct/aot/base.py +17 -12
  8. obstruct-0.2.4/src/obstruct/aot/pack.py +80 -0
  9. {obstruct-0.2.2 → obstruct-0.2.4}/src/obstruct/aot/unpack.py +43 -27
  10. {obstruct-0.2.2 → obstruct-0.2.4}/src/obstruct/methods.py +1 -1
  11. obstruct-0.2.4/test/test_speed.py +95 -0
  12. obstruct-0.2.2/.idea/modules.xml +0 -8
  13. obstruct-0.2.2/src/obstruct/__version__.py +0 -1
  14. obstruct-0.2.2/src/obstruct/aot/pack.py +0 -58
  15. obstruct-0.2.2/test/test_speed.py +0 -59
  16. {obstruct-0.2.2 → obstruct-0.2.4}/.idea/.gitignore +0 -0
  17. {obstruct-0.2.2 → obstruct-0.2.4}/.idea/inspectionProfiles/Project_Default.xml +0 -0
  18. {obstruct-0.2.2 → obstruct-0.2.4}/.idea/inspectionProfiles/profiles_settings.xml +0 -0
  19. {obstruct-0.2.2 → obstruct-0.2.4}/.idea/misc.xml +0 -0
  20. {obstruct-0.2.2 → obstruct-0.2.4}/.idea/vcs.xml +0 -0
  21. {obstruct-0.2.2 → obstruct-0.2.4}/pyproject.toml +0 -0
  22. {obstruct-0.2.2 → obstruct-0.2.4}/src/obstruct/__init__.py +0 -0
  23. {obstruct-0.2.2 → obstruct-0.2.4}/src/obstruct/aot/__init__.py +0 -0
  24. {obstruct-0.2.2 → obstruct-0.2.4}/src/obstruct/aot/aot.py +0 -0
  25. {obstruct-0.2.2 → obstruct-0.2.4}/src/obstruct/decorator.py +0 -0
  26. {obstruct-0.2.2 → obstruct-0.2.4}/src/obstruct/struct_type.py +0 -0
  27. {obstruct-0.2.2 → obstruct-0.2.4}/src/obstruct/types/__init__.py +0 -0
  28. {obstruct-0.2.2 → obstruct-0.2.4}/src/obstruct/types/base.py +0 -0
  29. {obstruct-0.2.2 → obstruct-0.2.4}/src/obstruct/types/types.py +0 -0
  30. {obstruct-0.2.2 → obstruct-0.2.4}/src/obstruct/types/types.pyi +0 -0
  31. {obstruct-0.2.2 → obstruct-0.2.4}/test/test.py +0 -0
  32. {obstruct-0.2.2 → obstruct-0.2.4}/uv.lock +0 -0
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="ProjectModuleManager">
4
+ <modules>
5
+ <module fileurl="file://$PROJECT_DIR$/.idea/obstruct.iml" filepath="$PROJECT_DIR$/.idea/obstruct.iml" />
6
+ </modules>
7
+ </component>
8
+ </project>
@@ -2,16 +2,13 @@
2
2
  <module external.system.id="pyproject.toml" type="PYTHON_MODULE" version="4">
3
3
  <component name="NewModuleRootManager">
4
4
  <content url="file://$MODULE_DIR$">
5
- <sourceFolder url="file://$MODULE_DIR$/src" />
5
+ <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
6
6
  <excludeFolder url="file://$MODULE_DIR$/.venv" />
7
7
  </content>
8
8
  <orderEntry type="jdk" jdkName="E:\.projects\obstruct\.venv" jdkType="Python SDK" />
9
9
  <orderEntry type="sourceFolder" forTests="false" />
10
10
  </component>
11
- <component name="PackageRequirementsSettings" />
12
11
  <component name="PyDocumentationSettings">
13
12
  <option name="format" value="PLAIN" />
14
13
  </component>
15
- <component name="ReSTService" />
16
- <component name="TestRunnerService" />
17
14
  </module>
@@ -4,13 +4,13 @@
4
4
  <option name="autoReloadType" value="SELECTIVE" />
5
5
  </component>
6
6
  <component name="ChangeListManager">
7
- <list default="true" id="b482b6b4-48ce-4be1-bf3c-a72ec254fa35" name="更改" comment="initial commit">
7
+ <list default="true" id="b482b6b4-48ce-4be1-bf3c-a72ec254fa35" name="更改" comment="修正了一些命名">
8
8
  <change beforePath="$PROJECT_DIR$/README.md" beforeDir="false" afterPath="$PROJECT_DIR$/README.md" afterDir="false" />
9
9
  <change beforePath="$PROJECT_DIR$/src/obstruct/aot/base.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/obstruct/aot/base.py" afterDir="false" />
10
+ <change beforePath="$PROJECT_DIR$/src/obstruct/aot/pack.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/obstruct/aot/pack.py" afterDir="false" />
10
11
  <change beforePath="$PROJECT_DIR$/src/obstruct/aot/unpack.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/obstruct/aot/unpack.py" afterDir="false" />
11
- <change beforePath="$PROJECT_DIR$/src/obstruct/decorator.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/obstruct/decorator.py" afterDir="false" />
12
12
  <change beforePath="$PROJECT_DIR$/src/obstruct/methods.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/obstruct/methods.py" afterDir="false" />
13
- <change beforePath="$PROJECT_DIR$/uv.lock" beforeDir="false" afterPath="$PROJECT_DIR$/uv.lock" afterDir="false" />
13
+ <change beforePath="$PROJECT_DIR$/test/test_speed.py" beforeDir="false" afterPath="$PROJECT_DIR$/test/test_speed.py" afterDir="false" />
14
14
  </list>
15
15
  <option name="SHOW_DIALOG" value="false" />
16
16
  <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -27,22 +27,22 @@
27
27
  <component name="Git.Settings">
28
28
  <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
29
29
  </component>
30
- <component name="GitHubPullRequestSearchHistory"><![CDATA[{
31
- "lastFilter": {
32
- "state": "OPEN",
33
- "assignee": "LittleNightSong"
30
+ <component name="GitHubPullRequestSearchHistory">{
31
+ &quot;lastFilter&quot;: {
32
+ &quot;state&quot;: &quot;OPEN&quot;,
33
+ &quot;assignee&quot;: &quot;LittleNightSong&quot;
34
34
  }
35
- }]]></component>
36
- <component name="GithubPullRequestsUISettings"><![CDATA[{
37
- "selectedUrlAndAccountId": {
38
- "url": "https://github.com/LittleNightSong/obstruct.git",
39
- "accountId": "c74c50b1-2437-429b-b395-d763b566b8a2"
35
+ }</component>
36
+ <component name="GithubPullRequestsUISettings">{
37
+ &quot;selectedUrlAndAccountId&quot;: {
38
+ &quot;url&quot;: &quot;https://github.com/LittleNightSong/obstruct.git&quot;,
39
+ &quot;accountId&quot;: &quot;c74c50b1-2437-429b-b395-d763b566b8a2&quot;
40
40
  }
41
- }]]></component>
42
- <component name="ProjectColorInfo"><![CDATA[{
43
- "associatedIndex": 3,
44
- "fromUser": false
45
- }]]></component>
41
+ }</component>
42
+ <component name="ProjectColorInfo">{
43
+ &quot;associatedIndex&quot;: 3,
44
+ &quot;fromUser&quot;: false
45
+ }</component>
46
46
  <component name="ProjectId" id="3HzAgHvw3X1T2NidzBlYQSHnZow" />
47
47
  <component name="ProjectViewState">
48
48
  <option name="hideEmptyMiddlePackages" value="true" />
@@ -54,6 +54,7 @@
54
54
  "Python.test.executor": "Run",
55
55
  "Python.test_speed.executor": "Run",
56
56
  "RunOnceActivity.ShowReadmeOnStart": "true",
57
+ "RunOnceActivity.git.unshallow": "true",
57
58
  "RunOnceActivity.typescript.service.memoryLimit.init": "true",
58
59
  "codeWithMe.voiceChat.enabledByDefault": "false",
59
60
  "git-widget-placeholder": "main",
@@ -69,7 +70,7 @@
69
70
  }]]></component>
70
71
  <component name="RunManager" selected="Python.test_speed">
71
72
  <configuration name="test" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
72
- <module name="Hydrogenlib-Objective-Struct" />
73
+ <module name="obstruct" />
73
74
  <option name="ENV_FILES" value="" />
74
75
  <option name="INTERPRETER_OPTIONS" value="" />
75
76
  <option name="PARENT_ENVS" value="true" />
@@ -93,7 +94,7 @@
93
94
  <method v="2" />
94
95
  </configuration>
95
96
  <configuration name="test_speed" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
96
- <module name="Hydrogenlib-Objective-Struct" />
97
+ <module name="obstruct" />
97
98
  <option name="ENV_FILES" value="" />
98
99
  <option name="INTERPRETER_OPTIONS" value="" />
99
100
  <option name="PARENT_ENVS" value="true" />
@@ -146,7 +147,31 @@
146
147
  <option name="project" value="LOCAL" />
147
148
  <updated>1786865208583</updated>
148
149
  </task>
149
- <option name="localTasksCounter" value="2" />
150
+ <task id="LOCAL-00002" summary="增强类型系统,撰写 README.md">
151
+ <option name="closed" value="true" />
152
+ <created>1786868730474</created>
153
+ <option name="number" value="00002" />
154
+ <option name="presentableId" value="LOCAL-00002" />
155
+ <option name="project" value="LOCAL" />
156
+ <updated>1786868730474</updated>
157
+ </task>
158
+ <task id="LOCAL-00003" summary="补充:基准测试脚本">
159
+ <option name="closed" value="true" />
160
+ <created>1786868746817</created>
161
+ <option name="number" value="00003" />
162
+ <option name="presentableId" value="LOCAL-00003" />
163
+ <option name="project" value="LOCAL" />
164
+ <updated>1786868746817</updated>
165
+ </task>
166
+ <task id="LOCAL-00004" summary="修正了一些命名">
167
+ <option name="closed" value="true" />
168
+ <created>1786870314349</created>
169
+ <option name="number" value="00004" />
170
+ <option name="presentableId" value="LOCAL-00004" />
171
+ <option name="project" value="LOCAL" />
172
+ <updated>1786870314349</updated>
173
+ </task>
174
+ <option name="localTasksCounter" value="5" />
150
175
  <servers />
151
176
  </component>
152
177
  <component name="TypeScriptGeneratedFilesManager">
@@ -165,6 +190,9 @@
165
190
  </component>
166
191
  <component name="VcsManagerConfiguration">
167
192
  <MESSAGE value="initial commit" />
168
- <option name="LAST_COMMIT_MESSAGE" value="initial commit" />
193
+ <MESSAGE value="增强类型系统,撰写 README.md" />
194
+ <MESSAGE value="补充:基准测试脚本" />
195
+ <MESSAGE value="修正了一些命名" />
196
+ <option name="LAST_COMMIT_MESSAGE" value="修正了一些命名" />
169
197
  </component>
170
198
  </project>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: obstruct
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Author-email: LittleNightSong <LittleNightSongYO@outlook.com>
5
5
  Classifier: Development Status :: 3 - Alpha
6
6
  Classifier: Programming Language :: Python
@@ -15,27 +15,48 @@ Description-Content-Type: text/markdown
15
15
  _来自已弃坑的 `Hydrogenlib`,重拾起当初的决心_
16
16
 
17
17
 
18
- >[!TIP]
18
+ > [!TIP]
19
19
  > 这个项目来自 [`HydrogenLib`](https://github.com/LittleNightSong/HydrogenLib)的 `Objective-Struct` 模块,脱离大项目的版本
20
20
  >
21
21
  > 同时,`HydrogenLib`中的 `Objective-Struct` 不再会更新
22
22
 
23
+ > [!TIP]
24
+ > 作者的主要开发精力放在了另一些项目上,也许很长时间这个项目都不会再更新
23
25
 
24
26
  # OBStruct
27
+
25
28
  _一个简单、纯粹、极速的二进制解析库_
26
29
 
27
30
  ## 亮点
31
+
32
+ - 纯 Python
28
33
  - 基于类型注解的字段声明方式
29
34
  - 支持 `struct` 标准库中的所有格式,并额外支持 array 类型
30
35
  - 基于 AOT 的加速
31
36
  - 完整的操作支持(打包、解包、流式处理)
32
37
 
38
+ > [!WARNING]
39
+ >使用 `array` 类型时,建议不要传入 list 类型或其它 Python 序列,
40
+ > 可能会带来极大的性能影响
41
+
42
+ ## 基准测试
43
+
44
+ ### 系统环境
45
+
46
+ - 12th Gen Intel® Core (TM) i7-12700H
47
+
48
+ 在 12个字段,10000 次操作,8轮取平均值的条件下:
49
+
50
+ - 打包:269.639ns 一次操作
51
+ - 解包:582.185ns 一次操作
52
+
33
53
  ## 安装
54
+
34
55
  ```commandline
35
56
  pip install obstruct
36
57
  ```
37
- 如果使用 `uv` 等包管理器,则按照工具的方法安装即可
38
58
 
59
+ 如果使用 `uv` 等包管理器,则按照工具的方法安装即可
39
60
 
40
61
  ## 用法
41
62
 
@@ -45,6 +66,7 @@ pip install obstruct
45
66
  import obstruct as obs
46
67
  from dataclasses import dataclass
47
68
 
69
+
48
70
  @obs.struct()
49
71
  @dataclass()
50
72
  class MyStruct(obs.Struct):
@@ -53,24 +75,25 @@ class MyStruct(obs.Struct):
53
75
  c: obs.long
54
76
  d: obs.unsigned_short
55
77
 
56
-
78
+
57
79
  if __name__ == '__main__':
58
80
  data = MyStruct(a=0, b=2.0, c=10000, d=14545)
59
-
81
+
60
82
  packed_data = data.pack()
61
83
  print(packed_data)
62
-
84
+
63
85
  restored = MyStruct.unpack(packed_data)
64
86
  print(restored)
65
-
87
+
66
88
  print(data == restored)
67
89
 
68
90
  ```
69
91
 
70
- 在上述示例中,你可能发现 `data != restored`,这是浮点数的精度限制导致的
71
- 一般来说,我们不建议使用浮点数类型
92
+ 在上述示例中,你可能发现 `data != restored`,这是浮点数的精度限制导致的。 因此,我们不建议使用浮点数类型,浮点数在二进制传输中也不常见。
72
93
 
73
94
  ## 计划
95
+
74
96
  - 展平装饰器,目前的 API 较为繁琐,需要简化为唯一的 `Struct` 基类
75
97
  - 继续优化性能
76
98
  - 通过存根文件强制表明类型似乎变得不再有效了
99
+ - 支持 Tag 联合类型,对标 `msgspec`
@@ -1,27 +1,48 @@
1
1
  _来自已弃坑的 `Hydrogenlib`,重拾起当初的决心_
2
2
 
3
3
 
4
- >[!TIP]
4
+ > [!TIP]
5
5
  > 这个项目来自 [`HydrogenLib`](https://github.com/LittleNightSong/HydrogenLib)的 `Objective-Struct` 模块,脱离大项目的版本
6
6
  >
7
7
  > 同时,`HydrogenLib`中的 `Objective-Struct` 不再会更新
8
8
 
9
+ > [!TIP]
10
+ > 作者的主要开发精力放在了另一些项目上,也许很长时间这个项目都不会再更新
9
11
 
10
12
  # OBStruct
13
+
11
14
  _一个简单、纯粹、极速的二进制解析库_
12
15
 
13
16
  ## 亮点
17
+
18
+ - 纯 Python
14
19
  - 基于类型注解的字段声明方式
15
20
  - 支持 `struct` 标准库中的所有格式,并额外支持 array 类型
16
21
  - 基于 AOT 的加速
17
22
  - 完整的操作支持(打包、解包、流式处理)
18
23
 
24
+ > [!WARNING]
25
+ >使用 `array` 类型时,建议不要传入 list 类型或其它 Python 序列,
26
+ > 可能会带来极大的性能影响
27
+
28
+ ## 基准测试
29
+
30
+ ### 系统环境
31
+
32
+ - 12th Gen Intel® Core (TM) i7-12700H
33
+
34
+ 在 12个字段,10000 次操作,8轮取平均值的条件下:
35
+
36
+ - 打包:269.639ns 一次操作
37
+ - 解包:582.185ns 一次操作
38
+
19
39
  ## 安装
40
+
20
41
  ```commandline
21
42
  pip install obstruct
22
43
  ```
23
- 如果使用 `uv` 等包管理器,则按照工具的方法安装即可
24
44
 
45
+ 如果使用 `uv` 等包管理器,则按照工具的方法安装即可
25
46
 
26
47
  ## 用法
27
48
 
@@ -31,6 +52,7 @@ pip install obstruct
31
52
  import obstruct as obs
32
53
  from dataclasses import dataclass
33
54
 
55
+
34
56
  @obs.struct()
35
57
  @dataclass()
36
58
  class MyStruct(obs.Struct):
@@ -39,24 +61,25 @@ class MyStruct(obs.Struct):
39
61
  c: obs.long
40
62
  d: obs.unsigned_short
41
63
 
42
-
64
+
43
65
  if __name__ == '__main__':
44
66
  data = MyStruct(a=0, b=2.0, c=10000, d=14545)
45
-
67
+
46
68
  packed_data = data.pack()
47
69
  print(packed_data)
48
-
70
+
49
71
  restored = MyStruct.unpack(packed_data)
50
72
  print(restored)
51
-
73
+
52
74
  print(data == restored)
53
75
 
54
76
  ```
55
77
 
56
- 在上述示例中,你可能发现 `data != restored`,这是浮点数的精度限制导致的
57
- 一般来说,我们不建议使用浮点数类型
78
+ 在上述示例中,你可能发现 `data != restored`,这是浮点数的精度限制导致的。 因此,我们不建议使用浮点数类型,浮点数在二进制传输中也不常见。
58
79
 
59
80
  ## 计划
81
+
60
82
  - 展平装饰器,目前的 API 较为繁琐,需要简化为唯一的 `Struct` 基类
61
83
  - 继续优化性能
62
84
  - 通过存根文件强制表明类型似乎变得不再有效了
85
+ - 支持 Tag 联合类型,对标 `msgspec`
@@ -0,0 +1 @@
1
+ version = "0.2.4"
@@ -31,18 +31,18 @@ class _objective_node_operator:
31
31
  self._node.body.append(tree)
32
32
  return tree
33
33
 
34
- # def insert_code(self, index, code: str):
35
- # tree = ast.parse(code)
36
- # if isinstance(tree, ast.Module):
37
- # for node in reversed(tree.body):
38
- # self._node.body.insert(index, node)
39
- # return tree
40
- # elif isinstance(tree, ast.Expr):
41
- # self._node.body.insert(index, tree.value)
42
- # return tree.value
43
- # else:
44
- # self._node.body.insert(index, tree)
45
- # return tree
34
+ def insert_code(self, index, code: str):
35
+ tree = ast.parse(code)
36
+ if isinstance(tree, ast.Module):
37
+ for node in reversed(tree.body):
38
+ self._node.body.insert(index, node)
39
+ return tree
40
+ elif isinstance(tree, ast.Expr):
41
+ self._node.body.insert(index, tree.value)
42
+ return tree.value
43
+ else:
44
+ self._node.body.insert(index, tree)
45
+ return tree
46
46
 
47
47
  def compile(self):
48
48
  module = ast.Module([self._node])
@@ -54,6 +54,7 @@ class _objective_node_operator:
54
54
  def exec(self, globals=None, locals=None, closure=None):
55
55
  globals = globals or {}
56
56
  exec(self.compile(), globals, locals, closure=closure)
57
+ # print(repr(globals))
57
58
  return globals[self._node.name]
58
59
 
59
60
  def generate_code(self):
@@ -137,5 +138,9 @@ def get_array_ctype(tp) -> str | None | Any:
137
138
  return get_ctype(get_array_spec(tp)[0])
138
139
 
139
140
 
141
+ def get_array_length(tp):
142
+ return get_array_spec(tp)[1]
143
+
144
+
140
145
  def to_tuple_expression(names, prefix='self.'):
141
146
  return ', '.join(f'{prefix}{name}' for name in names)
@@ -0,0 +1,80 @@
1
+ from __future__ import annotations
2
+
3
+ import array
4
+ import struct
5
+ import typing
6
+
7
+ from .base import unique_name_generator, is_struct, is_array, get_array_ctype, to_tuple_expression, function_def, \
8
+ get_array_length
9
+ from ..methods import get_ctype
10
+
11
+ if typing.TYPE_CHECKING:
12
+ from .. import struct_type
13
+
14
+
15
+ def create_pack_methods(cls: struct_type.Struct):
16
+ """
17
+ Generate `pack` and `pack_into` method
18
+ """
19
+ with (
20
+ function_def('pack(self)') as pack,
21
+ function_def('pack_into(self, buffer, offset=0)') as pack_into,
22
+ ):
23
+ ung = unique_name_generator()
24
+ flat_fields = []
25
+ array_fields = []
26
+
27
+ pack_into.add_code('buffer = memoryview(buffer)')
28
+
29
+ def add_code(code):
30
+ pack.add_code(code)
31
+ pack_into.add_code(code)
32
+
33
+ def walk(fields, path, parent_name=None, offset=0):
34
+ # parent name 标识了这个字段的父字段被赋值给了哪个临时变量,这样可以复用已有的临时变量,减少结构体属性访问次数
35
+ name = path[-1] # 这里的 name 是默认的 self,如果当前处理的是内嵌的结构体,那么 name 会被替换成临时变量
36
+
37
+ if len(path) > 1: # 当前处理的是内嵌结构体
38
+ name = next(ung) # 生成一个临时变量缓存其值
39
+ add_code(f'{name} = {parent_name}.{path[-1]}')
40
+
41
+ for field in fields: # 遍历结构体的所有字段
42
+ if is_struct(field.type): # 又是一个内嵌的结构体字段
43
+ walk(field.type.__cfields__, path + (field.name,), name, offset) # 递归
44
+
45
+ elif is_array(field.type): # 一个数组类型
46
+ dtype = get_array_ctype(field.type)
47
+ nbytes = struct.calcsize(dtype) * get_array_length(field.type)
48
+
49
+ _ = next(ung)
50
+ add_code(f'{_} = array({dtype!r}, {name}.{field.name})')
51
+ # flat_fields.append(f'{_}.tobytes()')
52
+ array_fields.append((_, offset, nbytes))
53
+
54
+ else:
55
+ flat_fields.append(f'{name}.{field.name}') # 普通的字段,添加到传递目标列表中
56
+
57
+ offset += struct.calcsize(get_ctype(field.type))
58
+
59
+ walk(cls.__cfields__, ('self',)) # 启动递归函数
60
+ # names = [field.name for field in fields]
61
+
62
+ expression = to_tuple_expression(flat_fields, prefix='')
63
+
64
+ if array_fields: # 如果有数组字段,我们需要手动操作缓冲区
65
+ pack.add_code(f"buffer = bytearray({cls.__cstruct__.size})") # 初始化缓冲区
66
+ for array_name, offset, size in array_fields:
67
+ add_code(f"buffer[{offset}:{offset + size}] = memoryview({array_name}).cast('B')")
68
+
69
+ pack.add_code(f"_pack_into(buffer, 0, {expression})") # 返回打包的结果,将所有字段参数展开传递
70
+ pack.add_return("buffer")
71
+ else:
72
+ pack.add_return(f"_pack({expression})")
73
+
74
+ pack_into.add_code(f"_pack_into(buffer, offset, {expression})")
75
+ print(pack.generate_code()) # Debug
76
+ print(pack_into.generate_code()) # Debug
77
+ return (
78
+ pack.exec({'_pack': cls.__cstruct__.pack, '_pack_into': cls.__cstruct__.pack_into, 'array': array.array}),
79
+ pack_into.exec({'_pack_into': cls.__cstruct__.pack_into, 'array': array.array})
80
+ )
@@ -1,9 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import array
4
+ import struct
4
5
  import typing
5
6
 
6
- from .base import unique_name_generator, is_struct, is_array, get_array_ctype, function_def, for_stmt
7
+ from .base import unique_name_generator, is_struct, is_array, get_array_ctype, function_def, for_stmt, get_array_length
8
+ from ..methods import get_ctype
7
9
 
8
10
  if typing.TYPE_CHECKING:
9
11
  from .. import struct_type
@@ -23,6 +25,8 @@ def create_unpack_methods(cls: type[struct_type.Struct]):
23
25
  iter_unpack.add_decorator('classmethod')
24
26
  iter_update.add_decorator('classmethod')
25
27
 
28
+ # unpack.add_code('buffer = memoryview(buffer)')
29
+
26
30
  ung = unique_name_generator()
27
31
 
28
32
  def update_add_code(code):
@@ -35,23 +39,25 @@ def create_unpack_methods(cls: type[struct_type.Struct]):
35
39
  unpack_from.add_code(code)
36
40
  iter_unpack_inner.add_code(code)
37
41
 
38
- post_processors = [] # 后处理代码列表
39
42
  assigns = [] # 赋值目标列表
40
43
  inner_classes = {} # 内嵌的结构体的类型映射(所有内嵌结构体的类型在生成代码时都会被代替为无意义的名称)
41
44
 
42
45
  iter_update.add_code("self = cls() if obj is None else obj")
43
46
 
44
- iter_unpack_inner = for_stmt("values in x_iter_unpack(buffer)")
45
- iter_update_inner = for_stmt("values in x_iter_unpack(buffer)")
47
+ iter_unpack_inner = for_stmt("values in _iter_unpack(buffer)")
48
+ iter_update_inner = for_stmt("values in _iter_unpack(buffer)")
46
49
 
47
50
  iter_unpack.add_node(iter_unpack_inner.node)
48
51
  iter_update.add_node(iter_update_inner.node)
49
52
 
50
- def walk(fields, path, parent_name=None, current_class_name='cls'):
53
+ array_fields = []
54
+
55
+ def walk(fields, path, parent_name=None, current_class_name='cls', offset=0):
51
56
  name = 'self'
52
57
 
58
+
53
59
  if len(path) > 1: # 在处理内嵌的结构体
54
- name = ung.next() # 临时变量的名称
60
+ name = next(ung) # 临时变量的名称
55
61
  update_add_code(f"{name} = {parent_name}.{path[-1]}")
56
62
 
57
63
  unpack_add_code(f"{parent_name}.{path[-1]} = {name} = __new__({current_class_name})") # 组合赋值
@@ -60,44 +66,54 @@ def create_unpack_methods(cls: type[struct_type.Struct]):
60
66
  # update 在这里不需要创建新对象
61
67
 
62
68
  for field in fields: # 遍历字段
63
- field_ref = f'{name}.{field.name}'
69
+ field_xpath = f'{name}.{field.name}'
64
70
 
65
71
  if is_struct(field.type): # 是个内嵌的结构体
66
72
  if field.type not in inner_classes: # 如果这个内嵌的结构体类型不在已知的结构体中(没有分配类名称)
67
- type_name = ung.next() # 生成一个唯一的类名
73
+ type_name = next(ung) # 生成一个唯一的类名
68
74
  inner_classes[field.type] = type_name # 添加
69
75
  else:
70
76
  type_name = inner_classes[field.type] # 如果在,那么提取
71
77
 
72
- walk(field.type.__cfields__, path + (field.name,), name, type_name)
78
+ walk(field.type.__cfields__, path + (field.name,), name, type_name, offset)
79
+
73
80
  elif is_array(field.type):
74
- d_ctype = get_array_ctype(field.type)
75
- post_processors.append(
76
- f'_ = array({d_ctype!r});'
77
- f'_.frombytes({field_ref});'
78
- f'{field_ref} = _'
79
- )
80
- assigns.append(field_ref)
81
+ dtype = get_array_ctype(field.type)
82
+ nbytes = get_array_length(field.type) * struct.calcsize(dtype)
83
+ array_fields.append((field_xpath, dtype, offset, nbytes))
84
+
81
85
  else:
82
- assigns.append(field_ref)
86
+ assigns.append(field_xpath)
87
+
88
+ offset += struct.calcsize(get_ctype(field.type))
83
89
 
84
90
  walk(cls.__cfields__, ('A',))
85
91
 
86
- assign_code = f"{', '.join(assigns)} = x_unpack(buffer)" # 生成普通的 unpack, update 用的赋值语句
92
+ assign_code = f"{', '.join(assigns)} = _unpack(buffer)" # 生成普通的 unpack, update 用的赋值语句
93
+ # {assigns} = _unpack(buffer)
87
94
  unpack.add_code(assign_code)
88
95
  update.add_code(assign_code)
89
96
 
90
- assign_code = assign_code.replace("x_unpack(buffer)", "values") # 生成 iter_xxx 的赋值语句
97
+ assign_code = assign_code.replace("_unpack(buffer)", "values") # 生成 iter_xxx 的赋值语句
98
+ # {assigns} = values
91
99
  iter_unpack_inner.add_code(assign_code)
92
100
  iter_update_inner.add_code(assign_code)
93
101
 
94
- assign_code = assign_code.replace("values", "x_unpack_from(buffer, offset)") # 生成 xxx_from 的赋值语句
102
+ assign_code = assign_code.replace("values", "_unpack_from(buffer, offset)") # 生成 xxx_from 的赋值语句
103
+ # {assigns} = _unpack_from(buffer, offset)
95
104
  unpack_from.add_code(assign_code)
96
105
  update_from.add_code(assign_code)
97
106
 
98
- for post_processor in post_processors:
99
- unpack_add_code(post_processor)
100
- update_add_code(post_processor)
107
+ for field_xpath, dtype, offset, nbytes in array_fields:
108
+ codes = [
109
+ f'{field_xpath} = _ = array({dtype!r})',
110
+ f'_.frombytes(buffer[{offset}:{offset+nbytes}])',
111
+ ]
112
+ for co in codes:
113
+ unpack.add_code(co)
114
+
115
+ if array_fields:
116
+ unpack.insert_code(0, 'buffer = memoryview(buffer)')
101
117
 
102
118
  unpack.add_return('self')
103
119
  update.add_return('self')
@@ -107,7 +123,7 @@ def create_unpack_methods(cls: type[struct_type.Struct]):
107
123
  iter_unpack_inner.add_code('yield self') # iter 方法是 yield,而非 return
108
124
  iter_update_inner.add_code('yield self')
109
125
 
110
- # print(unpack.generate_code())
126
+ print(unpack.generate_code())
111
127
  # print(update.generate_code())
112
128
  # print(unpack_from.generate_code())
113
129
  # print(update_from.generate_code())
@@ -120,9 +136,9 @@ def create_unpack_methods(cls: type[struct_type.Struct]):
120
136
 
121
137
  globals.update(dict(
122
138
  __new__=object.__new__,
123
- x_unpack=cls.__cstruct__.unpack,
124
- x_unpack_from=cls.__cstruct__.unpack_from,
125
- x_iter_unpack=cls.__cstruct__.iter_unpack,
139
+ _unpack=cls.__cstruct__.unpack,
140
+ _unpack_from=cls.__cstruct__.unpack_from,
141
+ _iter_unpack=cls.__cstruct__.iter_unpack,
126
142
  array=array.array
127
143
  ))
128
144
 
@@ -24,7 +24,7 @@ def get_ctype(tp) -> str:
24
24
  dtype = get_args(tp_args[0])[0]
25
25
  length = tp_args[1]
26
26
  c_size = struct.calcsize(get_ctype(dtype))
27
- return f'{length * c_size}s'
27
+ return f'{length * c_size}x'
28
28
  else:
29
29
  raise ValueError(f'Unrecognized type: {tp}')
30
30
 
@@ -0,0 +1,95 @@
1
+ import dataclasses
2
+ import time
3
+ from array import array
4
+ from typing import Annotated
5
+
6
+ import obstruct as obs
7
+
8
+
9
+ @obs.struct()
10
+ @dataclasses.dataclass(slots=True)
11
+ class MyStruct(obs.Struct):
12
+ a: int
13
+ b: int
14
+ c: float
15
+ d: int
16
+ e: obs.short
17
+ f: obs.unsigned_short
18
+ g: obs.long
19
+ h: obs.unsigned_long
20
+ i: obs.unsigned_long
21
+ j: obs.char
22
+ k: obs.unsigned_char
23
+ l: obs.long_long
24
+
25
+
26
+ @obs.struct()
27
+ @dataclasses.dataclass(slots=True)
28
+ class AnotherStruct(obs.Struct):
29
+ x: MyStruct
30
+ y: Annotated[array[int], 100]
31
+ z: MyStruct
32
+
33
+
34
+ data = MyStruct(
35
+ 1,2,1.0,4,5,6,7,8,9,b'8',1,2
36
+ )
37
+ packed_data = data.pack()
38
+
39
+
40
+ complex_data = AnotherStruct(data, array('i', [100]*100), data)
41
+ packed_complex_data = complex_data.pack()
42
+
43
+
44
+ def test_timeit(name, code):
45
+ import timeit
46
+
47
+ print("Test: ", name)
48
+ res = timeit.repeat(
49
+ code,
50
+ number=NUMBER,
51
+ repeat=REPEAT,
52
+ timer=time.perf_counter_ns,
53
+ globals=globals()
54
+ )
55
+
56
+ print(len(res))
57
+
58
+ for i in range(SORT):
59
+ res.remove(max(res))
60
+ res.remove(min(res))
61
+
62
+ print("Results Length:", len(res))
63
+
64
+ print(f'{NUMBER} steps finished in {sum(res)/(len(res)):.3f} ns')
65
+ print(f'Average: {sum(res)/(len(res))/NUMBER:.3f} ns per step', '\n\n')
66
+
67
+
68
+ print("\n\nMyStruct:", MyStruct.__cstruct__.size)
69
+ print("AnotherStruct:", AnotherStruct.__cstruct__.size, '\n\n')
70
+
71
+ NUMBER = 10000
72
+ REPEAT = 128
73
+ SORT = 32
74
+
75
+ test_timeit(
76
+ name='Pack',
77
+ code="""data.pack()""",
78
+ )
79
+
80
+ test_timeit(
81
+ name='Unpack',
82
+ code="""MyStruct.unpack(packed_data)""",
83
+ )
84
+
85
+
86
+
87
+ test_timeit(
88
+ name='Complex Pack',
89
+ code="""complex_data.pack()""",
90
+ )
91
+
92
+ test_timeit(
93
+ name='Complex Unpack',
94
+ code="""AnotherStruct.unpack(packed_complex_data)""",
95
+ )
@@ -1,8 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <project version="4">
3
- <component name="ProjectModuleManager">
4
- <modules>
5
- <module fileurl="file://$PROJECT_DIR$/.idea/Hydrogenlib-Objective-Struct.iml" filepath="$PROJECT_DIR$/.idea/Hydrogenlib-Objective-Struct.iml" />
6
- </modules>
7
- </component>
8
- </project>
@@ -1 +0,0 @@
1
- version = "0.2.2"
@@ -1,58 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import array
4
- import typing
5
-
6
- from .base import unique_name_generator, is_struct, is_array, get_array_ctype, to_tuple_expression, function_def
7
-
8
- if typing.TYPE_CHECKING:
9
- from .. import struct_type
10
-
11
-
12
- def create_pack_methods(cls: struct_type.Struct):
13
- """
14
- Generate `pack` and `pack_into` method
15
- """
16
- with (
17
- function_def('pack(self)') as pack,
18
- function_def('pack_into(self, buffer, offset=0)') as pack_into,
19
- ):
20
- ung = unique_name_generator()
21
- flat_fields = []
22
-
23
- def add_code(code):
24
- pack.add_code(code)
25
- pack_into.add_code(code)
26
-
27
- def walk(fields, path, parent_name=None):
28
- # parent name 标识了这个字段的父字段被赋值给了哪个临时变量,这样可以复用已有的临时变量,减少结构体属性访问次数
29
- name = 'self' # 这里的 name 是默认的 self,如果当前处理的是内嵌的结构体,那么 name 会被替换成临时变量
30
-
31
- if len(path) > 1: # 当前处理的是内嵌结构体
32
- name = ung.next() # 生成一个临时变量缓存其值
33
- add_code(f'{name} = {parent_name}.{path[-1]}')
34
-
35
- for field in fields: # 遍历结构体的所有字段
36
- if is_struct(field.type): # 又是一个内嵌的结构体字段
37
- walk(field.type.__cfields__, path + (field.name,), name) # 递归
38
- elif is_array(field.type): # 一个数组类型
39
- d_ctype = get_array_ctype(field.type)
40
-
41
- _ = ung.next()
42
- add_code(f'{_} = array({d_ctype!r}, {name}.{field.name})')
43
- flat_fields.append(f'{_}.tobytes()')
44
-
45
- else:
46
- flat_fields.append(f'{name}.{field.name}') # 普通的字段,添加到传递目标列表中
47
-
48
- walk(cls.__cfields__, ('self',)) # 启动递归函数
49
- # names = [field.name for field in fields]
50
-
51
- expression = to_tuple_expression(flat_fields, prefix='')
52
- pack.add_return(f"xpack({expression})") # 返回打包的结果,将所有字段参数展开传递
53
- pack_into.add_code(f"xpack_into(buffer, offset, {expression})")
54
- # print(pack.generate_code()) # Debug
55
- return (
56
- pack.exec({'xpack': cls.__cstruct__.pack, 'array': array.array}),
57
- pack_into.exec({'xpack_into': cls.__cstruct__.pack_into, 'array': array.array})
58
- )
@@ -1,59 +0,0 @@
1
- import dataclasses
2
- import time
3
-
4
- import obstruct as obs
5
-
6
-
7
- @obs.struct()
8
- @dataclasses.dataclass(slots=True)
9
- class MyStruct(obs.Struct):
10
- a: int
11
- b: int
12
- c: float
13
- d: int
14
- e: obs.short
15
- f: obs.unsigned_short
16
- g: obs.long
17
- h: obs.unsigned_long
18
- i: obs.unsigned_long
19
- j: obs.char
20
- k: obs.unsigned_char
21
- l: obs.long_long
22
-
23
-
24
-
25
- data = MyStruct(
26
- 1,2,1.0,4,5,6,7,8,9,b'8',1,2
27
- )
28
-
29
-
30
- packed_data = data.pack()
31
-
32
-
33
- def test_timeit(name, code):
34
- import timeit
35
-
36
- print("Test: ", name)
37
- res = timeit.timeit(
38
- code,
39
- number=NUMBER,
40
- timer=time.perf_counter_ns,
41
- globals=globals()
42
- )
43
- print(f'{NUMBER} steps finished in {res:.3f} ns')
44
- print(f'Average: {res/NUMBER:.3f} ns per step', '\n\n')
45
-
46
-
47
- NUMBER = 10000
48
-
49
- test_timeit(
50
- name='Pack',
51
- code="""data.pack()""",
52
- )
53
-
54
- test_timeit(
55
- name='Unpack',
56
- code="""MyStruct.unpack(packed_data)""",
57
- )
58
-
59
-
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes