求一个获取 lambda 对象源代码的方法 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
abersheeran

求一个获取 lambda 对象源代码的方法

  •  
  •   abersheeran Sep 25, 2021 3618 views
    This topic created in 1674 days ago, the information mentioned may be changed or developed.

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

    • 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 的玩意。但是卡在了这里。

    18 replies    2021-09-26 11:07:16 +08:00
    chinvo
        1
    chinvo  
       Sep 25, 2021   1
    python 不清楚, C# 里面的 lambda 会被编译成 expression, 就能直接用了.

    python 大概也有类似机制?
    joApioVVx4M4X6Rf
        2
    joApioVVx4M4X6Rf  
       Sep 26, 2021
    同问
    hsfzxjy
        3
    hsfzxjy  
       Sep 26, 2021 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  
       Sep 26, 2021 via Android
    @hsfzxjy #3 这个在 3.5-3.10 应该都可以,2.7.没测过
    penguinWWY
        5
    penguinWWY  
       Sep 26, 2021
    先说这个问题,瞎猜一下

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

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

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


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


    @chenxytw 这个办法我也想过,问题在于重载运算符不能把 and 、or 、not 运算给重载了……
    penguinWWY
        11
    penguinWWY  
       Sep 26, 2021
    @abersheeran 当然是使用 C API 辣
    O5oz6z3
        12
    O5oz6z3  
       Sep 26, 2021
    本质上也许是寻找一个表达式的源码位置:
    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  
       Sep 26, 2021 via Android
    @penguinWWY co_linetable 应该只存了行号,co_columntable 能拿到列号,但这是 3.10 新加的,兼容性不太好
    hsfzxjy
        14
    hsfzxjy  
       Sep 26, 2021 via Android
    @abersheeran 我当时的解决方法是强制用户给同一行的 lambda 加不同的前缀,当然这个就比较丑了。同期待更好的方法
    abersheeran
        15
    abersheeran  
    OP
       Sep 26, 2021
    @O5oz6z3 一语惊醒梦中人,只要对比源码编译后的 __code__ 就行了。一行最多也就几个 lambda 。


    @hsfzxjy 好家伙,不同前缀有点暴力了
    O5oz6z3
        16
    O5oz6z3  
       Sep 26, 2021
    @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  
       Sep 26, 2021   1
    @O5oz6z3 #16 co_code 不可靠,一个简单的反例

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

    这是因为变量名一类的不存在于字节码中,而是在 __code__.co_names 里
    hsfzxjy
        18
    hsfzxjy  
       Sep 26, 2021   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 有很多很多
    About     Help     Advertise     Blog     API     FAQ     Solana     6078 Online   Highest 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 74ms UTC 02:39 PVG 10:39 LAX 19:39 JFK 22:39
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86