我先说一下我试过的方法,以及为什么不行:

inspect.getsource:这玩意只能获取*行,比如定义一个多行的 lambda 它也只能拿到*行。并且如果 lambda 前面还有东西,它会一并拿回来,这部满足我的需求,我只想要从 lambda 关键词开始到整个 lambda 结束的定义,是不是原有格式我不在乎,只要完整且不多余就行。
lambda_object.__code__.co_firstlineno:这个同上,实际上 inspect.getsource 就是用这个值去拿的。这个值只能标识*行所在,却不能标识开始的横轴位置以及*后的坐标。
我能想到的解决方式是直接拿到 lambda 对象的字节码,从字节码反编译到 Python 源代码,但是我需要 2.7 和 3.9 、3.10 三个版本同时兼容的……我对字节码反编译不太熟悉,目前找到的都是以文件为单位的反编译,不知道有没有以对象为代码的反编译库。

我想做一个把类似于 User.filter(lambda user: user.age > 18) 这样的语句翻译到 SQL 的玩意。但是卡在了这里。

lambda 字节码 tso urce18 条回复 • 2021-09-26 11:07:16 +08:00
chinvo 1
chinvo 1 天前 ❤️ 1
python 不清楚, C# 里面的 lambda 会被编译成 expression, 就能直接用了.

python 大概也有类似机制?
v2exblog 2
v2exblog 1 天前
同问
hsfzxjy 3
hsfzxjy 1 天前 via Android
我之前实现一个拿 lambda 的 AST 的功能,但这个有个限制就是需要保证前置的 token 是固定的,具体可参考 https://github.com/hsfzxjy/lambdex/blob/master/lambdex/utils/ast.py#L97

比如要求用户写成 def_(lambda: …) ,然后将这个 lambda 对象以及字符串 ‘def_’ 传入这个函数,就可以拿到 AST

可能对你有帮助
hsfzxjy 4
hsfzxjy 1 天前 via Android
@hsfzxjy #3 这个在 3.5-3.10 应该都可以,2.7.没测过
penguinWWY 5
penguinWWY 1 天前
先说这个问题,瞎猜一下

一个方法是通过这个 lambda 反向拿到 module,然后把这个 py 文件编译到 ast 再做遍历

另一个是 PyCodeObject 对象中有一个属性是 co_linetable,这个属性的类型是一个 PyBytesObject,可以看 cpython 中对它的解析方法,应该可以拿到起始行列和终止行列
https://github.com/python/cpython/blob/main/Include/cpython/code.h#L77
penguinWWY 6
penguinWWY 1 天前
@hsfzxjy
@abersheeran

再借楼说下,https://www.v2ex.com/t/804224#reply4
二位有没有兴趣
chenxytw 7
chenxytw 1 天前 ❤️ 1
你的问题本身我不是很了解….但从你要做的事情来看,我在想,是不是没必要用你题目中提出的方法。而是通过实现 User 里 age 的 `__gt__` 之类的魔术方法,在这之中保存一些状态,然后将 Model 本身传给这个 lambda 就能做到你想要的事情了…这应该是*常见的实现类似事情的做法了….当然因为你要做的事情没有详细描述,所以不知道是不是有什么需求导致了你不采用这种方案….
fgwmlhdkkkw 8
fgwmlhdkkkw 1 天前 via Android
@chenxytw Python 的 orm 都是这么做的。
2i2Re2PLMaDnghL 9
2i2Re2PLMaDnghL 1 天前
参考下 PyMacro ?
abersheeran 10
abersheeran 1 天前
@hsfzxjy 你这个思路我也想到过,但是有一个问题我不知道该如何解决,比如同一行出现两个 lambda……

@penguinWWY 在 CPython 运行时用 Python 拿 PyBytesObject 的原始指针做不到的吧?

@chenxytw 这个办法我也想过,问题在于重载运算符不能把 and 、or 、not 运算给重载了……
penguinWWY 11
penguinWWY 1 天前
@abersheeran 当然是使用 C API 辣
O5oz6z3 12
O5oz6z3 1 天前
本质上也许是寻找一个表达式的源码位置:
1. lambda 有多少行?
2. 同一行里有几个 lambda ?
3. 是否嵌套 lambda ?
4. 是否有’lambda’字面字符串?
http://xion .io/post/code/python-get-lambda-code.html
看到这篇文章和#3 楼的实现,想到一个未验证的思路:对 inspect.getsource() 获取的源码进行修剪并编译成 ast,遍历 ast 提取所有 lambda 节点,用 Python3.9 的 ast.unparse() 获取近似的源码,用 co_code 判断源码编译后是否等价。
hsfzxjy 13
hsfzxjy 1 天前 via Android
@penguinWWY co_linetable 应该只存了行号,co_columntable 能拿到列号,但这是 3.10 新加的,兼容性不太好
hsfzxjy 14
hsfzxjy 1 天前 via Android
@abersheeran 我当时的解决方法是强制用户给同一行的 lambda 加不同的前缀,当然这个就比较丑了。同期待更好的方法
abersheeran 15
abersheeran 1 天前
@O5oz6z3 一语惊醒梦中人,只要对比源码编译后的 __code__ 就行了。一行*多也就几个 lambda 。

@hsfzxjy 好家伙,不同前缀有点暴力了
O5oz6z3 16
O5oz6z3 1 天前
@abersheeran #15 我指的是 __code__.co_code 字节码,是从那篇文章中学来的,虽然我也不确定这个字节码比较是否可靠。顺便写了两个 demo 。
简单的情况:
source_text = inspect.getsourcelines(lambda_func)[0][0]
source_ast = ast.parse(source_text)
lambda_node = next((node for node in ast.walk(source_ast) if isinstance(node, ast.Lambda)), None)
lambda_text = ast.unparse(lambda_node)
复杂的情况:
text = ‘lambda’ + inspect.getsource(lambda_func).partition(‘lambda’)[2].rstrip()
while text:
… try:
… … tree = ast.parse(‘({})’.format(text))
… … srcs = [ast.unparse(node) for node in ast.walk(tree) if isinstance(node, ast.Lambda)]
… … break
… except SyntaxError:
… … text = text[:-1]
test = lambda src: compile(src,”,’eval’).co_consts[0].co_code==lambda_func.__code__.co_code
hits = list(filter(test, srcs))
hsfzxjy 17
hsfzxjy 1 天前 ❤️ 1
@O5oz6z3 #16 co_code 不可靠,一个简单的反例

(lambda: print(1)).__code__.co_code == (lambda: sum(1)).__code__.co_code # True

这是因为变量名一类的不存在于字节码中,而是在 __code__.co_names 里
hsfzxjy 18
hsfzxjy 1 天前 ❤️ 2
还有另一个问题是你要考虑 lambda 所在的闭包,看一个例子

def f():
… a = 1
… return lambda: a + 1
a = 1
f().__code__.co_code == (lambda: a + 1).__code__.co_code # False

这两个 lambda 虽然代码相同但是他们字节码不一样。原因是 f() 中的 a 是个 local 变量,读取时会使用 LOAD_DEREF ;而后一个是 global 变量,读取时会使用 LOAD_GLOBAL 。总而言之 corner cases 有很多很多