我先说一下我试过的方法,以及为什么不行:
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 的玩意。但是卡在了这里。
1
chinvo 2021-09-25 23:51:31 +08:00 1
python 不清楚, C# 里面的 lambda 会被编译成 expression, 就能直接用了.
python 大概也有类似机制? |
2
joApioVVx4M4X6Rf 2021-09-26 00:55:54 +08:00
同问
|
3
hsfzxjy 2021-09-26 01:14:04 +08:00 via Android
我之前实现一个拿 lambda 的 AST 的功能,但这个有个限制就是需要保证前置的 token 是固定的,具体可参考 https://github.com/hsfzxjy/lambdex/blob/master/lambdex/utils/ast.py#L97
比如要求用户写成 def_(lambda: ...) ,然后将这个 lambda 对象以及字符串 'def_' 传入这个函数,就可以拿到 AST 可能对你有帮助 |
5
penguinWWY 2021-09-26 02:14:52 +08:00
先说这个问题,瞎猜一下
一个方法是通过这个 lambda 反向拿到 module,然后把这个 py 文件编译到 ast 再做遍历 另一个是 PyCodeObject 对象中有一个属性是 co_linetable,这个属性的类型是一个 PyBytesObject,可以看 cpython 中对它的解析方法,应该可以拿到起始行列和终止行列 https://github.com/python/cpython/blob/main/Include/cpython/code.h#L77 |
6
penguinWWY 2021-09-26 02:16:30 +08:00
|
7
chenxytw 2021-09-26 04:54:44 +08:00 1
你的问题本身我不是很了解....但从你要做的事情来看,我在想,是不是没必要用你题目中提出的方法。而是通过实现 User 里 age 的 `__gt__` 之类的魔术方法,在这之中保存一些状态,然后将 Model 本身传给这个 lambda 就能做到你想要的事情了...这应该是最常见的实现类似事情的做法了....当然因为你要做的事情没有详细描述,所以不知道是不是有什么需求导致了你不采用这种方案....
|
8
fgwmlhdkkkw 2021-09-26 07:32:37 +08:00 via Android
@chenxytw Python 的 orm 都是这么做的。
|
9
2i2Re2PLMaDnghL 2021-09-26 09:40:36 +08:00
参考下 PyMacro ?
|
10
abersheeran OP @hsfzxjy 你这个思路我也想到过,但是有一个问题我不知道该如何解决,比如同一行出现两个 lambda……
@penguinWWY 在 CPython 运行时用 Python 拿 PyBytesObject 的原始指针做不到的吧? @chenxytw 这个办法我也想过,问题在于重载运算符不能把 and 、or 、not 运算给重载了…… |
11
penguinWWY 2021-09-26 10:17:44 +08:00
@abersheeran 当然是使用 C API 辣
|
12
O5oz6z3 2021-09-26 10:29:52 +08:00
本质上也许是寻找一个表达式的源码位置:
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 判断源码编译后是否等价。 |
13
hsfzxjy 2021-09-26 10:33:17 +08:00 via Android
@penguinWWY co_linetable 应该只存了行号,co_columntable 能拿到列号,但这是 3.10 新加的,兼容性不太好
|
14
hsfzxjy 2021-09-26 10:35:24 +08:00 via Android
@abersheeran 我当时的解决方法是强制用户给同一行的 lambda 加不同的前缀,当然这个就比较丑了。同期待更好的方法
|
15
abersheeran OP |
16
O5oz6z3 2021-09-26 10:53:35 +08:00
@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)) |
17
hsfzxjy 2021-09-26 10:59:28 +08:00 1
@O5oz6z3 #16 co_code 不可靠,一个简单的反例
(lambda: print(1)).__code__.co_code == (lambda: sum(1)).__code__.co_code # True 这是因为变量名一类的不存在于字节码中,而是在 __code__.co_names 里 |
18
hsfzxjy 2021-09-26 11:07:16 +08:00 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 有很多很多 |