Python技术迷

将 Python代码,自动转成其他编程语言代码

很多人写着写着 Python,某天领导一句话:“这个脚本能不能弄成 Java 版 / Go 版?最好自动一点。” 你当时心里想的估计跟我当年一样:自动转换?不就是“翻译代码”嘛,看起来简单,真干起来全是坑。

1. 先搞清楚:我们在“自动转”的到底是什么?

很多人一上来就想:“我正则替换一下 def 变成 function,True 变 true,不就完了?” 这种方式写个小 demo 还能唬人,一上业务代码就彻底崩溃。

真正靠谱的自动转换,基本都是这三步:

  1. 把 Python 源码解析成 AST(抽象语法树)
  2. 把 AST 映射成一个中间表示(IR)或者直接映射成目标语言的 AST
  3. 再把目标 AST“打印”成另一门语言的源码

也就是说,你不是在“字符串替换代码”,而是在“先理解代码,再用另一门语言重新说一遍同样的意思”。

Python 已经给我们准备好了第一步的工具,就是标准库里的 ast 模块。

来个最小可用例子:我们写一个“只会翻译简单算术表达式到 JavaScript”的迷你转换器。

import ast

classPyToJs(ast.NodeVisitor):
defvisit_Module(self, node):
        lines = [self.visit(stmt) for stmt in node.body]
return"\n".join(lines)

defvisit_Expr(self, node):
# 表达式语句,比如 "1 + 2"
return self.visit(node.value) + ";"

defvisit_Assign(self, node):
# 只处理 a = 1 这种简单赋值
        target = self.visit(node.targets[0])
        value = self.visit(node.value)
returnf"let {target} = {value};"

defvisit_Name(self, node):
return node.id

defvisit_Constant(self, node):
return repr(node.value)

defvisit_BinOp(self, node):
        left = self.visit(node.left)
        right = self.visit(node.right)
        op = self._binop_symbol(node.op)
returnf"({left}{op}{right})"

def_binop_symbol(self, op):
        mapping = {
            ast.Add: "+",
            ast.Sub: "-",
            ast.Mult: "*",
            ast.Div: "/",
        }
for k, v in mapping.items():
if isinstance(op, k):
return v
raise NotImplementedError(f"暂不支持的操作符: {op}")

defpy_to_js(src: str) -> str:
    tree = ast.parse(src)
return PyToJs().visit(tree)

if __name__ == "__main__":
    py_code = """
a = 1 + 2 * 3
b = a - 4
b
"""

    print(py_to_js(py_code))

运行大概会输出这样:

let a = (1 + (2 * 3));
let b = (a - 4);
b;

这个玩意当然很弱,但它是真正意义上的“自动转语言”的雏形: ——先用 Python 的 AST 把代码理解一遍,再输出另一种语言。

这套路跟很多数据库之间的语法转换、主从复制的日志解析其实是一个思路:先抽象成“中立的结构”,再做映射。

2. 真正的难点在哪?不是语法,而是“世界观”

语法其实是最容易解决的,最多就是多写几个 visit_XXX。 真正麻烦的是 两门语言的“世界观”不一样,主要集中在几块:

2.1 动态 vs 静态

Python 是动态类型,你可以这样胡来:

x = 1
x = "hello"
x = [1, 2, 3]

你要转成 Java / Go 这种静态类型语言,就会立刻被问三个问题:

  1. x 的类型到底是什么?
  2. 如果不同分支给 x 赋了不同类型怎么办?
  3. 函数参数 / 返回值类型怎么填?

所以实际做工程级转换,通常会要求:

  • 要么约定“只支持一部分写法”,比如禁止同一个变量反复换类型
  • 要么强制你在关键地方加类型标注(type hints),让工具有信息可用

比如:

defadd(a: int, b: int) -> int:
return a + b

这时候要生成 Java 代码就简单多了,基本能机械地翻成:

intadd(int a, int b){
return a + b;
}

如果你完全不写类型标注,就指望自动工具猜,基本上最后会走向两种结局:

  • 生成的目标语言代码到处是 Object / any,可读性极差
  • 转出来还得人帮它“人工补类型”,工作量并没有少多少

2.2 语法糖和语言特性

Python 有很多别的语言没有的语法糖,比如:

  • 列表推导式:[x * 2 for x in arr if x > 10]
  • 生成器:yield
  • 上下文管理器:with open(...) as f:
  • 装饰器:@cache、@login_required 等

你要转到别的语言,通常有几种做法:

  1. 翻译成等价但“丑一点”的写法比如把列表推导式改成 for 循环加 if。

  2. 在目标语言里写一层“运行时库”比如你把很多 Python 内置行为压到一个 runtime.py / runtime.js 或 runtime.java 里,自动转换器转出来的代码大量调用这个库。

  3. 干脆不支持这种特性转换前用静态检查工具扫一遍,遇到“不支持”的用法直接报错。

你会发现这跟消息队列里那些“保证不丢消息”的机制很像:为了换取一个统一的模型,背后必须有一堆看不见的包装和约束。

2.3 标准库和第三方库

还有一个很现实的问题: 你 Python 里随手写了:

import requests

resp = requests.get("https://example.com")
print(resp.json())

你转到 Go / JavaScript,requests 这个库在那边根本不存在。 这时候就要引入“库映射表”的概念:

  • requests.get 在 JavaScript 里用 fetch 或 axios.get
  • 在 Java 里可能映射成 HttpClient
  • 在 Go 里就是 http.Get

自动化程度最高的做法,是维护一份 mapping 配置:

LIB_MAP = {
    ("requests", "get"): {
"js": "axios.get",
"go": "http.Get",
"java": "HttpClient.get",
    }
}

转换器在处理 requests.get 调用时,按照目标语言挑一个合适的名字替换过去。 这个 mapping 一开始很少,只覆盖你自己项目里用到的那一部分即可,慢慢长大。

3. 写一个“可扩展”的小型转换器雏形

上面那个 PyToJs 只支持极简语法,没法应对真实项目。 如果你真想在公司里搞一个“实用一点”的版本,建议把结构写得再工程化一些,比如:

import ast
from dataclasses import dataclass
from typing import Any, Dict, List

@dataclass
classIRNode:
    kind: str
    value: Any = None
    children: List["IRNode"] = None
    extra: Dict[str, Any] = None

classPyToIR(ast.NodeVisitor):
defvisit_Module(self, node):
return IRNode(
            kind="module",
            children=[self.visit(stmt) for stmt in node.body],
        )

defvisit_FunctionDef(self, node):
return IRNode(
            kind="func",
            value=node.name,
            children=[self.visit(stmt) for stmt in node.body],
            extra={
"args": [arg.arg for arg in node.args.args],
"returns": ast.unparse(node.returns) if node.returns elseNone,
            }
        )

defvisit_Return(self, node):
return IRNode(
            kind="return",
            children=[self.visit(node.value)] if node.value else [],
        )

defvisit_Call(self, node):
return IRNode(
            kind="call",
            value=self.visit(node.func),
            children=[self.visit(arg) for arg in node.args],
        )

defvisit_Name(self, node):
return IRNode(kind="name", value=node.id)

defvisit_Constant(self, node):
return IRNode(kind="const", value=node.value)

# 其他节点按需补…

classIRToJs:
defemit(self, node: IRNode, indent: int = 0) -> str:
        sp = " " * (indent * 4)
if node.kind == "module":
return"\n".join(self.emit(child, indent) for child in node.children)
if node.kind == "func":
            args = ", ".join(node.extra["args"])
            body = "\n".join(self.emit(c, indent + 1) for c in node.children)
returnf"{sp}function {node.value}({args}) {{\n{body}\n{sp}}}"
if node.kind == "return":
if node.children:
returnf"{sp}return {self.emit(node.children[0])};"
returnf"{sp}return;"
if node.kind == "call":
            callee = self.emit(node.value)
            args = ", ".join(self.emit(a) for a in node.children)
returnf"{callee}({args})"
if node.kind == "name":
return node.value
if node.kind == "const":
return repr(node.value)
raise NotImplementedError(node.kind)

defpy_to_js_advanced(src: str) -> str:
    tree = ast.parse(src)
    ir = PyToIR().visit(tree)
return IRToJs().emit(ir)

if __name__ == "__main__":
    py_code = """
def add(a: int, b: int) -> int:
    return a + b

print(add(1, 2))
"""


    print(py_to_js_advanced(py_code))

这里我刻意多绕了一层 IR(中间表示),好处是:

  • 以后你想支持“Python -> Java”、“Python -> Go”,只要再写一个 IRToJava / IRToGo,前面的 PyToIR 不用动;
  • IR 本身是你自己定义的,可以做到尽量中立,不偏向某一门语言;
  • 你还能对 IR 做一些简单的分析,比如控制流、依赖关系、变量收集等。

这个结构跟很多数据库复制、日志解析、网络协议栈拆包的做法很像: 先把“各种乱七八糟的输入”打成统一结构,然后再往目标世界一个个适配。

4. 工程实践里,怎么把“自动转”落到地上?

说白了,你不太可能做出一个“任意 Python 代码,100% 无损转成任何语言”的神器。 比较现实的路线,一般是这样的:

  1. 锁定场景比如:“我们公司一堆 Python 数据处理脚本,要迁到 Node.js 做统一平台”; 那你的转换器就只需要支持这一批脚本里真用到的语法和库。

  2. 约束子集明确写进 README: 比如“不支持多重继承、不支持元类、不支持动态 import、不支持在函数里随手改模块变量类型”等。

  3. 要求关键位置加类型标注尤其是公共 API、核心类、对外暴露的函数。 没有类型,转换器连大致的目标类型都猜不出来,只能疯狂打补丁。

  4. 维护一份“库映射表”和“运行时库”

  • 映射表负责:requests.get → axios.get 这种名字替换;
  • 运行时库负责:在目标语言里补一层 py_range, py_len, py_enumerate 之类的函数,让转换出来的代码可以跑。
  • 上测试这一步很关键:转换之后立刻跑原来的单元测试 / 集成测试, 做不到自动修复,至少能自动告诉你“哪一块转坏了”,人工去改也会轻松很多。

  • 从投入产出比上看,这类工具最适合的场景是:

    • 同一业务逻辑需要在多端实现(比如后端、前端、边缘节点);
    • 或者把一堆旧 Python 脚本、规则引擎、算法代码迁到一个统一平台;
    • 又或者做“跨语言 SDK 生成器”:写一份“接口描述 + Python 示例”,自动生成 Java / Go / TS 的 SDK 壳子。

    自动把 Python 代码转成其它语言,本质是:用机器干“看懂代码再用另一门语言重写”的活儿,而不是做字符串替换。

    -END-

    我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html

    🔥虎哥私藏精品🔥

    虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB