pythonalize 0.0.1__py3-none-any.whl

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.
@@ -0,0 +1,5 @@
1
+ """Pythonalize: Python 的本地化表示层(简体中文)。"""
2
+
3
+ __version__ = "0.0.1"
4
+
5
+ __all__ = ["__version__"]
@@ -0,0 +1,5 @@
1
+ """``python -m pythonalize`` 入口,委托给 :func:`pythonalize.cli.main`。"""
2
+
3
+ from .cli import main
4
+
5
+ raise SystemExit(main())
pythonalize/cli.py ADDED
@@ -0,0 +1,168 @@
1
+ """Pythonalize 的命令行入口。
2
+
3
+ 本模块只负责参数解析与文件调度:把用户请求映射到
4
+ :mod:`pythonalize.normalize`、:mod:`pythonalize.represent` 与
5
+ :mod:`pythonalize.runner` 的既有功能,不复制任何转换核心逻辑。
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+ import sys
12
+ import tokenize
13
+ from pathlib import Path
14
+ from typing import Callable, Optional, Sequence
15
+
16
+ from .normalize import normalize
17
+ from .represent import represent
18
+ from .runner import compile_file, normalized_source, read_source, run_file
19
+
20
+ __all__ = ["main"]
21
+
22
+
23
+ def _file_error(path: str, want_ext: Optional[str]) -> Optional[str]:
24
+ """校验 ``path`` 是否存在、是否为文件、扩展名是否匹配,返回错误消息。"""
25
+ p = Path(path)
26
+ if not p.exists():
27
+ return f"{path} does not exist."
28
+ if not p.is_file():
29
+ return f"{path} is not a file."
30
+ if want_ext is not None and p.suffix != want_ext:
31
+ return f"{path} must be a {want_ext} file."
32
+ return None
33
+
34
+
35
+ def _read_python_source(path: str) -> str:
36
+ """按 Python 源码编码 (PEP 263) 读取 ``path``。"""
37
+ with tokenize.open(path) as f:
38
+ return f.read()
39
+
40
+
41
+ def _write_conversion(
42
+ path: str,
43
+ dest_ext: str,
44
+ source_reader: Callable[[str], str],
45
+ encoder: Callable[[str], str],
46
+ yes: bool,
47
+ ) -> int:
48
+ """把 ``path`` 经 ``encoder`` 转换后写到 ``dest_ext`` 目标文件。
49
+
50
+ 目标用 ``with_suffix`` 派生;已存在时报告错误并返回非零,``yes``
51
+ 也不覆盖。未给 ``-y`` 时交互式询问,仅 ``y``/``Y`` 才以 ``x`` 独占
52
+ 模式创建,拒绝则返回 0。
53
+ """
54
+ target = Path(path).with_suffix(dest_ext)
55
+ if target.exists():
56
+ print(f"Error: {target} already exists.", file=sys.stderr)
57
+ return 1
58
+
59
+ # 先完成读取与编码,再询问是否创建,避免在用户确认后才做转换准备。
60
+ content = encoder(source_reader(path))
61
+
62
+ if not yes:
63
+ answer = input(f"Create {target}? [y/N]: ")
64
+ if answer.lower() != "y":
65
+ return 0
66
+
67
+ try:
68
+ with target.open("x", encoding="utf-8") as f:
69
+ f.write(content)
70
+ except FileExistsError:
71
+ print(f"Error: {target} already exists.", file=sys.stderr)
72
+ return 1
73
+ return 0
74
+
75
+
76
+ def _cmd_run(args: argparse.Namespace) -> int:
77
+ """执行 ``.pthz`` 文件。程序自身异常不被吞掉,直接向外传播。"""
78
+ err = _file_error(args.file, ".pthz")
79
+ if err is not None:
80
+ print(f"Error: {err}", file=sys.stderr)
81
+ return 1
82
+ run_file(args.file)
83
+ return 0
84
+
85
+
86
+ def _cmd_show(args: argparse.Namespace) -> int:
87
+ """在 stdout 输出 ``.pthz`` 规范化后的源码。"""
88
+ err = _file_error(args.file, ".pthz")
89
+ if err is not None:
90
+ print(f"Error: {err}", file=sys.stderr)
91
+ return 1
92
+ print(normalized_source(args.file), end="")
93
+ return 0
94
+
95
+
96
+ def _cmd_check(args: argparse.Namespace) -> int:
97
+ """编译 ``.pthz`` 文件;成功打印 ``OK: FILE``。"""
98
+ err = _file_error(args.file, ".pthz")
99
+ if err is not None:
100
+ print(f"Error: {err}", file=sys.stderr)
101
+ return 1
102
+ compile_file(args.file)
103
+ print(f"OK: {args.file}")
104
+ return 0
105
+
106
+
107
+ def _cmd_represent(args: argparse.Namespace) -> int:
108
+ """把 ``.py`` 转换为 ``.pthz`` (``represent``)。"""
109
+ err = _file_error(args.file, ".py")
110
+ if err is not None:
111
+ print(f"Error: {err}", file=sys.stderr)
112
+ return 1
113
+ return _write_conversion(args.file, ".pthz", _read_python_source, represent, args.yes)
114
+
115
+
116
+ def _cmd_normalize(args: argparse.Namespace) -> int:
117
+ """把 ``.pthz`` 转换为 ``.py`` (``normalize``)。"""
118
+ err = _file_error(args.file, ".pthz")
119
+ if err is not None:
120
+ print(f"Error: {err}", file=sys.stderr)
121
+ return 1
122
+ return _write_conversion(args.file, ".py", read_source, normalize, args.yes)
123
+
124
+
125
+ def _add_run_subparser(subparsers: argparse._SubParsersAction, name: str) -> None:
126
+ p = subparsers.add_parser(name, help="run a .pthz file")
127
+ p.add_argument("file", help="path to the .pthz file")
128
+ p.set_defaults(handler=_cmd_run)
129
+
130
+
131
+ def _build_parser() -> argparse.ArgumentParser:
132
+ parser = argparse.ArgumentParser(prog="pthz", description="Run or convert Pythonalize sources.")
133
+ parser.add_argument("--version", action="version", version="%(prog)s 0.0.1")
134
+
135
+ subparsers = parser.add_subparsers(dest="command", required=True)
136
+
137
+ _add_run_subparser(subparsers, "run")
138
+
139
+ show = subparsers.add_parser("show", help="print normalized source of a .pthz file")
140
+ show.add_argument("file", help="path to the .pthz file")
141
+ show.set_defaults(handler=_cmd_show)
142
+
143
+ check = subparsers.add_parser("check", help="compile a .pthz file")
144
+ check.add_argument("file", help="path to the .pthz file")
145
+ check.set_defaults(handler=_cmd_check)
146
+
147
+ represent_parser = subparsers.add_parser("represent", help="convert .py to .pthz")
148
+ represent_parser.add_argument("file", help="path to the .py file")
149
+ represent_parser.add_argument("-y", "--yes", action="store_true", help="create without prompting")
150
+ represent_parser.set_defaults(handler=_cmd_represent)
151
+
152
+ normalize_parser = subparsers.add_parser("normalize", help="convert .pthz to .py")
153
+ normalize_parser.add_argument("file", help="path to the .pthz file")
154
+ normalize_parser.add_argument("-y", "--yes", action="store_true", help="create without prompting")
155
+ normalize_parser.set_defaults(handler=_cmd_normalize)
156
+
157
+ return parser
158
+
159
+
160
+ def main(argv: Optional[Sequence[str]] = None) -> int:
161
+ """CLI 入口,返回进程退出码。"""
162
+ words = list(sys.argv[1:] if argv is None else argv)
163
+ # 裸首个文件参数 (如 ``pthz FILE``) 视为 ``run FILE``。
164
+ commands = ("run", "show", "check", "represent", "normalize")
165
+ if words and words[0] not in commands and not words[0].startswith("-"):
166
+ words.insert(0, "run")
167
+ args = _build_parser().parse_args(words)
168
+ return args.handler(args)
@@ -0,0 +1,46 @@
1
+ """把简体中文 Pythonalize 表示源码规范化为 canonical Python 源码。
2
+
3
+ 仅对词法层面的 :data:`tokenize.NAME` 令牌做映射:字符串字面量、注释、
4
+ 数字以及映射表中不存在的 (未知) 标识符一律保持不变,因此天然支持
5
+ 中英混写。
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import io
11
+ import tokenize
12
+ from typing import Optional
13
+
14
+ from .scheme import Scheme, load_scheme
15
+
16
+ __all__ = ["normalize"]
17
+
18
+ #: 惰性加载一次已校验的 scheme,供 :func:`normalize` 使用。
19
+ _SCHEME: Optional[Scheme] = None
20
+
21
+
22
+ def _scheme() -> Scheme:
23
+ """返回缓存的 :class:`Scheme`,首次调用时通过 :func:`load_scheme` 加载。"""
24
+ global _SCHEME
25
+ if _SCHEME is None:
26
+ _SCHEME = load_scheme()
27
+ return _SCHEME
28
+
29
+
30
+ def normalize(source: str) -> str:
31
+ """把 ``source`` 中的 Pythonalize 词替换为对应的 Python 词。
32
+
33
+ 仅替换 :data:`tokenize.NAME` 且命中 ``load_scheme().e2p`` 的令牌;
34
+ 字符串、注释、数字、未知标识符保持不变。
35
+ """
36
+ e2p = _scheme().e2p
37
+
38
+ result = []
39
+ for ttype, string, (srow, scol), (erow, ecol), line in tokenize.generate_tokens(
40
+ io.StringIO(source).readline
41
+ ):
42
+ if ttype == tokenize.NAME and string in e2p:
43
+ string = e2p[string]
44
+ result.append((ttype, string, (srow, scol), (erow, ecol), line))
45
+
46
+ return tokenize.untokenize(result)
@@ -0,0 +1,51 @@
1
+ """把 canonical Python 源码转换回简体中文 Pythonalize 表示源码。
2
+
3
+ 仅对词法层面的 :data:`tokenize.NAME` 令牌做映射:字符串字面量、注释、
4
+ 数字以及映射表中不存在的 (未知) 标识符一律保持不变,因此天然支持
5
+ 中英混写。
6
+
7
+ 注意:本函数纯按词法替换,内置名称(如 ``print``、``len``)只要命中
8
+ ``load_scheme().p2e`` 就会替换成对应的 Pythonalize 词,无法识别变量遮蔽
9
+ (shadowing)等语义层面信息。
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import io
15
+ import tokenize
16
+ from typing import Optional
17
+
18
+ from .scheme import Scheme, load_scheme
19
+
20
+ __all__ = ["represent"]
21
+
22
+ #: 惰性加载一次已校验的 scheme,供 :func:`represent` 使用。
23
+ _SCHEME: Optional[Scheme] = None
24
+
25
+
26
+ def _scheme() -> Scheme:
27
+ """返回缓存的 :class:`Scheme`,首次调用时通过 :func:`load_scheme` 加载。"""
28
+ global _SCHEME
29
+ if _SCHEME is None:
30
+ _SCHEME = load_scheme()
31
+ return _SCHEME
32
+
33
+
34
+ def represent(source: str) -> str:
35
+ """把 ``source`` 中的 Python 词替换为对应的 Pythonalize 词。
36
+
37
+ 仅替换 :data:`tokenize.NAME` 且命中 ``load_scheme().p2e`` 的令牌;
38
+ 字符串、注释、数字、未知标识符保持不变。内置名称按词法替换,
39
+ 无法识别遮蔽。
40
+ """
41
+ p2e = _scheme().p2e
42
+
43
+ result = []
44
+ for ttype, string, (srow, scol), (erow, ecol), line in tokenize.generate_tokens(
45
+ io.StringIO(source).readline
46
+ ):
47
+ if ttype == tokenize.NAME and string in p2e:
48
+ string = p2e[string]
49
+ result.append((ttype, string, (srow, scol), (erow, ecol), line))
50
+
51
+ return tokenize.untokenize(result)
pythonalize/runner.py ADDED
@@ -0,0 +1,52 @@
1
+ """执行 Pythonalize 源码:读取、规范化、编译并运行。
2
+
3
+ 模块只关心从 ``.pthz`` 文件到可执行代码的完整流程:读取 UTF-8 源码、
4
+ 用 :mod:`pythonalize.normalize` 转为 canonical Python、编译并在一个独立的全局
5
+ 命名空间中执行,不生成任何临时文件。
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import pathlib
11
+ from typing import Any, Dict
12
+
13
+ from .normalize import normalize
14
+
15
+ __all__ = ["read_source", "normalized_source", "compile_file", "run_file"]
16
+
17
+
18
+ def read_source(path: str) -> str:
19
+ """读取 ``path`` 指向的文件(UTF-8),返回其文本内容。"""
20
+ return pathlib.Path(path).read_text(encoding="utf-8")
21
+
22
+
23
+ def normalized_source(path: str) -> str:
24
+ """读取 ``path`` 的源码并返回 :func:`normalize` 后的 canonical Python。"""
25
+ return normalize(read_source(path))
26
+
27
+
28
+ def compile_file(path: str) -> Any:
29
+ """把 ``path`` 的规范化源码编译为 code 对象。
30
+
31
+ ``filename`` 使用传入的原始 ``path``,以便 traceback / 调试信息指向
32
+ 原始的 ``.pthz`` 文件。
33
+ """
34
+ return compile(normalized_source(path), str(path), "exec")
35
+
36
+
37
+ def run_file(path: str) -> Dict[str, Any]:
38
+ """执行 ``path`` 指向的 ``.pthz`` 文件,返回其全局命名空间。
39
+
40
+ 不生成临时文件:直接 :func:`exec` 编译得到的 code 对象。全局命名空间
41
+ 至少包含 ``__name__ == "__main__"``、``__file__ == str(path)``、
42
+ ``__package__ is None`` 与 ``__cached__ is None``。
43
+ """
44
+ code = compile_file(path)
45
+ namespace: Dict[str, Any] = {
46
+ "__name__": "__main__",
47
+ "__file__": str(path),
48
+ "__package__": None,
49
+ "__cached__": None,
50
+ }
51
+ exec(code, namespace)
52
+ return namespace
pythonalize/scheme.py ADDED
@@ -0,0 +1,130 @@
1
+ """Pythonalize 翻译方案(scheme)的加载与校验。
2
+
3
+ 通过 :mod:`importlib.resources` 从已安装包(``pythonalize.schemes``)中读取
4
+ JSON 资源,校验其结构(meta 与 keywords/constants/builtins 均为非空的
5
+ ``str -> str`` 映射),并构造统一的 Pythonalize -> Python 合并映射及严格反向
6
+ 映射。
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ from importlib.resources import files
13
+ from typing import Dict, Mapping
14
+
15
+ __all__ = ["Scheme", "load_scheme", "default_language"]
16
+
17
+ #: 默认加载的 scheme 语言标识。
18
+ default_language = "zh-CN"
19
+
20
+
21
+ class Scheme:
22
+ """一个校验通过的 Pythonalize 翻译方案。
23
+
24
+ Attributes
25
+ ----------
26
+ meta_name / meta_locale / meta_version:
27
+ scheme 的元数据。
28
+ keywords / constants / builtins:
29
+ 各分组原始 ``str -> str`` 映射。
30
+ e2p:
31
+ 合并后的 Pythonalize 词 -> Python 词映射。
32
+ p2e:
33
+ 严格的反向映射(Python -> Pythonalize),唯一的。
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ meta_name: str,
39
+ meta_locale: str,
40
+ meta_version: str,
41
+ keywords: Mapping[str, str],
42
+ constants: Mapping[str, str],
43
+ builtins: Mapping[str, str],
44
+ ) -> None:
45
+ self.meta_name = meta_name
46
+ self.meta_locale = meta_locale
47
+ self.meta_version = meta_version
48
+ self.keywords: Dict[str, str] = dict(keywords)
49
+ self.constants: Dict[str, str] = dict(constants)
50
+ self.builtins: Dict[str, str] = dict(builtins)
51
+ self.e2p: Dict[str, str] = {}
52
+ self.p2e: Dict[str, str] = {}
53
+ self._build()
54
+
55
+ def _build(self) -> None:
56
+ """构建合并映射与严格反向映射,Python 值重复时抛出 :class:`ValueError`。"""
57
+ e2p: Dict[str, str] = {}
58
+ p2e: Dict[str, str] = {}
59
+ for group in (self.keywords, self.constants, self.builtins):
60
+ for e, p in group.items():
61
+ if p in p2e and p2e[p] != e:
62
+ raise ValueError(
63
+ f"Python 值 {p!r} 被多个 Pythonalize 词重复映射: "
64
+ f"{p2e[p]!r} 与 {e!r}"
65
+ )
66
+ e2p[e] = p
67
+ p2e[p] = e
68
+ self.e2p = e2p
69
+ self.p2e = p2e
70
+
71
+ def __repr__(self) -> str:
72
+ return (
73
+ f"Scheme(locale={self.meta_locale!r}, version={self.meta_version!r}, "
74
+ f"words={len(self.e2p)})"
75
+ )
76
+
77
+
78
+ def _validate_mapping(name: str, value: object) -> Dict[str, str]:
79
+ """校验 ``value`` 是非空的 ``str -> str`` 映射,并返回其副本。"""
80
+ if not isinstance(value, dict):
81
+ raise ValueError(f"{name} 必须是一个 str->str 映射(对象)")
82
+ result: Dict[str, str] = {}
83
+ for key, item in value.items():
84
+ if not isinstance(key, str) or not key:
85
+ raise ValueError(f"{name} 的键必须是非空字符串")
86
+ if not isinstance(item, str) or not item:
87
+ raise ValueError(f"{name}[{key!r}] 的值必须是非空字符串")
88
+ result[key] = item
89
+ if not result:
90
+ raise ValueError(f"{name} 不能为空")
91
+ return result
92
+
93
+
94
+ def load_scheme(entry: str = default_language) -> Scheme:
95
+ """从已安装包加载名为 ``entry`` 的 scheme 并校验。
96
+
97
+ ``entry`` 对应资源 ``pythonalize.schemes/<entry>.json``。返回的
98
+ :class:`Scheme` 已完成 schema 校验,并计算好 ``e2p`` / ``p2e``。
99
+ """
100
+ resource = files("pythonalize.schemes").joinpath(f"{entry}.json")
101
+ raw = json.loads(resource.read_text(encoding="utf-8"))
102
+
103
+ if not isinstance(raw, dict):
104
+ raise ValueError("scheme 顶层必须是一个对象")
105
+
106
+ meta = raw.get("meta")
107
+ if not isinstance(meta, dict):
108
+ raise ValueError("scheme 缺少 meta 对象(meta 必须是一个对象)")
109
+ for key in ("name", "locale", "version"):
110
+ value = meta.get(key)
111
+ if not isinstance(value, str) or not value:
112
+ raise ValueError(f"meta.{key} 必须是非空字符串")
113
+
114
+ keywords = _validate_mapping("keywords", raw.get("keywords"))
115
+ constants = _validate_mapping("constants", raw.get("constants"))
116
+ builtins = _validate_mapping("builtins", raw.get("builtins"))
117
+
118
+ if meta["locale"] != entry:
119
+ raise ValueError(
120
+ f"meta.locale {meta['locale']!r} 与请求的 entry {entry!r} 不匹配"
121
+ )
122
+
123
+ return Scheme(
124
+ meta_name=meta["name"],
125
+ meta_locale=meta["locale"],
126
+ meta_version=meta["version"],
127
+ keywords=keywords,
128
+ constants=constants,
129
+ builtins=builtins,
130
+ )
@@ -0,0 +1,4 @@
1
+ """翻译方案(scheme)资源包。
2
+
3
+ 每个 ``.json`` 文件描述一种语言的 Pythonalize -> Python 词表。
4
+ """
@@ -0,0 +1,62 @@
1
+ {
2
+ "meta": {
3
+ "name": "Pythonalize 简体中文 Scheme",
4
+ "locale": "zh-CN",
5
+ "version": "0.0.1"
6
+ },
7
+ "keywords": {
8
+ "定义": "def",
9
+ "若": "if",
10
+ "否则若": "elif",
11
+ "否则": "else",
12
+ "返回": "return",
13
+ "遍历": "for",
14
+ "只要": "while",
15
+ "停止": "break",
16
+ "继续": "continue",
17
+ "在": "in",
18
+ "是": "is",
19
+ "非": "not",
20
+ "且": "and",
21
+ "或": "or",
22
+ "尝试": "try",
23
+ "如果察觉": "except",
24
+ "保底": "finally",
25
+ "报错": "raise",
26
+ "导入": "import",
27
+ "从": "from",
28
+ "作为": "as",
29
+ "经由": "with",
30
+ "类": "class",
31
+ "略过": "pass"
32
+ },
33
+ "constants": {
34
+ "真": "True",
35
+ "假": "False",
36
+ "空": "None"
37
+ },
38
+ "builtins": {
39
+ "打印": "print",
40
+ "长度": "len",
41
+ "范围": "range",
42
+ "枚举": "enumerate",
43
+ "配对": "zip",
44
+ "总和": "sum",
45
+ "最小": "min",
46
+ "最大": "max",
47
+ "排序": "sorted",
48
+ "列表": "list",
49
+ "字典": "dict",
50
+ "集合": "set",
51
+ "组": "tuple",
52
+ "字符串": "str",
53
+ "整数": "int",
54
+ "小数": "float",
55
+ "是或非": "bool",
56
+ "输入": "input",
57
+ "打开": "open",
58
+ "类型": "type",
59
+ "全部": "all",
60
+ "任一": "any"
61
+ }
62
+ }
@@ -0,0 +1,258 @@
1
+ Metadata-Version: 2.4
2
+ Name: pythonalize
3
+ Version: 0.0.1
4
+ Summary: Pythonalize: a localized representation layer for Python (Simplified Chinese).
5
+ License: MIT License
6
+
7
+ Copyright (c) 2026 Pythonalize contributors
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
26
+
27
+ Requires-Python: >=3.9
28
+ Description-Content-Type: text/markdown
29
+ License-File: LICENSE
30
+ Dynamic: license-file
31
+
32
+ # Pythonalize
33
+
34
+ **Pythonalize** 是 Python 的本地化表示层:让你用简体中文写 Python。
35
+
36
+ 它不是新语言,也不是翻译器之外的东西。它只做一件事——把中文写法的
37
+ Python 源码,换成等价的英文 Python 源码,再交给 Python 执行。
38
+
39
+ - 面向:想用中文阅读/编写 Python 的人,以及用 AI 快速出代码的 **vibe coder**。
40
+ - 它**降低源码的阅读门槛**,但**不会替代 Python 概念学习**——你要理解
41
+ 什么是函数、循环、类,仍是学 Python 本身。
42
+
43
+ ---
44
+
45
+ ## Pythonalize 是什么 / 不是什么
46
+
47
+ **是:**
48
+
49
+ - Python 的一个 **本地化表示层**——中文只是 Python 关键字/内置名的另一种写法。
50
+ - 一个用 `tokenize` 做**词法映射**的小工具:把中文 token 换成英文 token。
51
+
52
+ **不是:**
53
+
54
+ - ❌ 一门**新语言**。
55
+ - ❌ 一个新的 **runtime / 解释器**。
56
+ - ❌ 一个新的 **VM**。
57
+ - ❌ 一个 **compiler**(它只是把源码换成等价源码,不生成机器码)。
58
+ - ❌ 一个**独立的 package ecosystem**(你不用装另一套包管理器)。
59
+ - ❌ 一个**自然语言编程**系统(它是词法映射,不是理解你的程序意图)。
60
+
61
+ **黄金规则:** Python 的 **grammar(语法)和 semantics(语义)是唯一标准**。
62
+ Pythonalize 只改变字面写法,完全不改变 Python 的语法与运行语义。
63
+
64
+ > **Pythonalize does not define program semantics. Python does.**
65
+
66
+ ---
67
+
68
+ ## 中英混写与标识符
69
+
70
+ - 可以**中英混写**:中文词换英文词,其余一律原样保留。
71
+ - **字符串、注释、数字** 绝不会被替换。
72
+ - **项目自定义的中文 Unicode 标识符**(如函数名、变量名、类名)**不被猜译**,
73
+ 原样保留。Pythonalize 只替换它内置映射表里的那几十个词。
74
+
75
+ ---
76
+
77
+ ## 版本
78
+
79
+ - `v0.0.1` 目前**仅支持 `zh-CN`**(简体中文)一个语言。
80
+ - 其他语言 / 更多词表将在后续版本加入。
81
+
82
+ ---
83
+
84
+ ## 安装
85
+
86
+ 安装发布版:
87
+
88
+ ```bash
89
+ pip install pythonalize
90
+ ```
91
+
92
+ 从 PyPI 安装 `pythonalize` 发布版,执行需要 Python 3.9 及以上。
93
+
94
+ 从源码开发安装(在项目根目录执行):
95
+
96
+ ```bash
97
+ pip install .
98
+ ```
99
+
100
+ 这会从当前源码安装,是开发时用到的方式;执行同样需要 Python 3.9 及以上。
101
+
102
+ 安装后,`pthz` 命令进入 PATH(由 `pyproject.toml` 的 `[project.scripts]` 提供)。
103
+
104
+ ---
105
+
106
+ ## Hello 示例
107
+
108
+ `examples/hello.pthz`:
109
+
110
+ ```python
111
+ 定义 主():
112
+ 打印("你好,世界")
113
+
114
+ 若 __name__ == "__main__":
115
+ 主()
116
+ ```
117
+
118
+ 它等价于 `examples/hello.py`:
119
+
120
+ ```python
121
+ def main():
122
+ print("你好,世界")
123
+
124
+ if __name__ == "__main__":
125
+ main()
126
+ ```
127
+
128
+ 两个文件**语义等价**,运行都输出:
129
+
130
+ ```text
131
+ 你好,世界
132
+ ```
133
+
134
+ ---
135
+
136
+ ## 命令
137
+
138
+ 所有命令都是 `pthz` 的子命令。
139
+
140
+ ### 运行(默认)
141
+
142
+ ```bash
143
+ pthz examples/hello.pthz # 裸文件 = run
144
+ pthz run examples/hello.pthz # 等价
145
+ ```
146
+
147
+ ### 查看规范化结果(推荐先看再跑)
148
+
149
+ ```bash
150
+ pthz show examples/hello.pthz
151
+ ```
152
+
153
+ 把 `.pthz` 规范化后的等价 Python 打印到屏幕,方便你**审阅**。
154
+
155
+ ### 语法检查
156
+
157
+ ```bash
158
+ pthz check examples/hello.pthz
159
+ ```
160
+
161
+ 只编译、不执行。成功打印 `OK: examples/hello.pthz`。
162
+
163
+ ### 双向转换
164
+
165
+ **注意:** 目标文件默认是源文件同名的另一后缀(`hello.pthz` ↔ `hello.py`)。
166
+ 如果目标**已存在**,命令会拒绝(`-y` 也不会覆盖)。仓库自带的
167
+ `examples/hello.py` 与 `examples/hello.pthz` **同时存在**,因此不要把其中
168
+ 一个当另一个的转换目标——先复制到一个**目标初始不存在**的新目录。
169
+
170
+ 下面的示例分成两个相互独立的目录,每个方向只执行一条命令,可整段照抄。
171
+
172
+ **`.pthz` → `.py`(中文写 → 英文写),用 `from-pthz/`:**
173
+
174
+ ```bash
175
+ mkdir -p from-pthz && cp examples/hello.pthz from-pthz/
176
+ pthz normalize -y from-pthz/hello.pthz # 创建 from-pthz/hello.py
177
+ ```
178
+
179
+ **`.py` → `.pthz`(英文写 → 中文写),用 `from-py/`:**
180
+
181
+ ```bash
182
+ mkdir -p from-py && cp examples/hello.py from-py/
183
+ pthz represent -y from-py/hello.py # 创建 from-py/hello.pthz
184
+ ```
185
+
186
+ > 上面用 `-y` 跳过确认、直接创建。去掉 `-y` 会先显示
187
+ > `Create <TARGET>? [y/N]: ` 提示,等你在输入 `y` 或 `Y` 之后才创建。
188
+
189
+ 转换行为:
190
+
191
+ - 目标文件默认是源文件同名的另一后缀(`hello.pthz` ↔ `hello.py`)。
192
+ - 目标**已存在时拒绝**,只报 `Error: <TARGET> already exists.` 并返回非零。
193
+ - **`-y` 也不会覆盖已有文件**,它只是跳过“是否创建?”的询问。
194
+ - 没有 `-y` 时会询问 `Create <TARGET>? [y/N]: `,只有输入 `y` 或 `Y`
195
+ 才创建;回答其他内容则正常退出(返回 0)。
196
+
197
+ ### 帮助与版本
198
+
199
+ ```bash
200
+ pthz --help
201
+ pthz --version
202
+ ```
203
+
204
+ `pthz --help` 列出全部子命令与参数;`pthz --version` 打印当前版本
205
+ (`pthz 0.0.1`)。
206
+
207
+ ---
208
+
209
+ ## 一个需要知道的坑:内置名遮蔽也会被替换
210
+
211
+ Pythonalize 是**纯词法映射**,不追踪变量绑定。
212
+
213
+ 也就是说:即使你在代码里把一个内置名(如 `print`、`len`、`range`)
214
+ 当普通变量重新赋值了,它的那个名字**依然会被替换**成对应的中文词。
215
+ 它**无法识别 shadowing(遮蔽)**。
216
+
217
+ 如果你要遮蔽内置名,请注意:被替换的仍然是那个词法 token。
218
+
219
+ ```python
220
+ # .py 里:把 print 重新赋值,再调用
221
+ print = my_logger # 这个 print 会被替换成 打印
222
+ print("hi") # 这个也会被替换成 打印
223
+ ```
224
+
225
+ ---
226
+
227
+ ## ⚠️ 安全提示:不是沙箱
228
+
229
+ **执行 `.pthz` 与执行等价 `.py` 一样不安全。**
230
+
231
+ - Pythonalize **不是沙箱**,没有隔离能力。
232
+ - 被运行的代码拥有与你完全相同的权限:能读写文件、访问网络、调用系统命令。
233
+ - **运行前**请务必先用 `pthz show` 查看规范化结果、再用 `pthz check`,
234
+ 并**审阅**代码内容,尤其是你从 AI 或其他渠道获得的代码。
235
+
236
+ ---
237
+
238
+ ## 给 AI 生成代码的建议
239
+
240
+ 如果你用 AI 帮你写 Pythonalize 代码:
241
+
242
+ - 直接告诉它:“用 Pythonalize 写,输出 **`.pthz`**,语法就是 Python,只是把
243
+ `def/if/for/print` 等词换成了中文对应词。”
244
+ - 让它**先写 `.pthz`**,你再 `pthz show` 检查;或让它**同时给 `.py`**,
245
+ 你用 `pthz represent` 转成 `.pthz` 再运行。
246
+ - ⚠️ 交代它:**不要编造 Pythonalize 不存在的语法**;Pythonalize 只用标准 Python
247
+ 语法,只是关键字/内置名是中文。
248
+ - 让 AI 遵守 `zh-CN` 当前词表(`定义/若/否则若/否则/返回/遍历/只要/停止/
249
+ 继续/在/是/非/且/或/从/作为/类/略过/打印/长度/范围/枚举/总和/最小/最大...
250
+ 等),避免它发明新词。
251
+ - 提醒它:**项目自定义的中文标识符、字符串、注释不会被翻译**,别假设会被猜译。
252
+ - **别让 AI 在没有审阅的情况下直接运行**外部代码——先 `show`/`check` 再看。
253
+
254
+ ---
255
+
256
+ ## License
257
+
258
+ MIT —— 详见 [`LICENSE`](LICENSE)。
@@ -0,0 +1,15 @@
1
+ pythonalize/__init__.py,sha256=7XMdW4NRg9AJ5XdiLlKuqR5_OlPkewx9RvhNgV2xV9U,119
2
+ pythonalize/__main__.py,sha256=m6bmOhrPyXIihKIKXOH87zTQmtZ8zDbh5Z3LzXcCIPY,132
3
+ pythonalize/cli.py,sha256=3EbIq6UAwYcq1zuLbSGlvNRDD4mJ_9957KrWEMBfRGo,5994
4
+ pythonalize/normalize.py,sha256=ExC8hk_clQSF1C15z3fLHbOryPrqcn4SzcoD3AsOIJs,1413
5
+ pythonalize/represent.py,sha256=_TXuY8qCwvRX0Ys8R165MVw7C3YhnFTWc3IpM8E90VQ,1690
6
+ pythonalize/runner.py,sha256=6RTzjisc7V79HsQDnZvRu--v2LndRxjPfOt7lYYNqMA,1756
7
+ pythonalize/scheme.py,sha256=dm1uAfStMesyIeDeNwT-_x-PvYRGWZoKHQqaV_mO78A,4598
8
+ pythonalize/schemes/__init__.py,sha256=Cd0I3n0NSfqjVzS-kR8exEquFSfQzGblcK9YhUGzy5Y,122
9
+ pythonalize/schemes/zh-CN.json,sha256=_QAm7WYVpksrQXYbadW7axMf1WYX4k5BVjZZVqHaWE8,1236
10
+ pythonalize-0.0.1.dist-info/licenses/LICENSE,sha256=kv85h4RPRAABEGCfsS-XAv8PgmVHaVWBK9aZrseAtw4,1081
11
+ pythonalize-0.0.1.dist-info/METADATA,sha256=kl6wvl1xFMhiLHYPjHksF-kfVy7RutqnAA-lbG6xDQo,8577
12
+ pythonalize-0.0.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
13
+ pythonalize-0.0.1.dist-info/entry_points.txt,sha256=z31X4E5B1I_Vt5N8c-MXu9rVg-2DCbZOqna7Jfub5TM,46
14
+ pythonalize-0.0.1.dist-info/top_level.txt,sha256=7m04orXMDgeOz-2FY5LYkdE9_0iA13FQt2oMj-kgx30,12
15
+ pythonalize-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ pthz = pythonalize.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pythonalize contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ pythonalize