V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
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
O5oz6z3
V2EX  ›  Python

突然想到 py 一个简单的函数字符串缩写语法糖,不知道有没有现成的?

  •  
  •   O5oz6z3 · 2021-08-08 12:27:23 +08:00 · 2571 次点击
    这是一个创建于 963 天前的主题,其中的信息可能已经有所发展或是发生改变。
    import magic
    
    # 1 lambda 匿名函数表达式识别:没有分号`;`或者无括号参数声明 `args:`
    magic | "_: _.attr+1"
    magic | "*a,**k: a[0].attr+1"
    
    # 2 def 多行函数识别:检测分号`;`或者括号参数声明`(args):`或者函数声明`def (args):` 
    magic | "_.attr+= 1; yield _.attr"
    magic | "(*a,**k): b=a[0].attr+1; yield b"
    magic | "def(*a,**k): b=a[0].attr+1; yield b"
    magic | "def *a,**k: b=a[0].attr+1; yield b"
    
    # 3 传递位置参数引用:等价于 partial(func, *args),参数从左侧开始绑定。
    magic | ["a,b: a+b", a, b]
    
    # 4 传递关键字参数引用:等价于 partial(func, **kwrds)
    magic("a,b,c=None: c or a+b", c=c)
    
    # 5 同时传递位置参数和关键字参数引用:等价于 partial(func, *args, **kwrds)
    magic("a,b,c=None: c or a+b", a, b, c=c)
    
    # 6 隐式参数:`_`
    magic | "_.attr+1"
    magic | "def: b=_.attr+1; yield b"
    

    突然想到这么一个简单的语法糖格式,不知道是否已经被人提出实现过了?还是说这种魔法语法糖没有实用价值?那么有没有类似的纯字符串 DSL 语法糖库?或者大家有什么其他看法?

    原理是将多行函数或函数表达式的缩写写在字符串里,然后构造出函数。

    实现思路是简单地检测字符串,也可以用正则,甚至手写解释器。 转换回完整 python 函数也简单,补充完整函数声明然后 eval/exec 。

    缺点是传值、闭包引用会比较麻烦,虽然应该可以通过 inspect 实现,但这么复杂和不纯函数不建议用字符串缩写。如果是完整的函数代码字符串,那根本就不需要缩写了。

    优点是导入即可使用。而且可以进一步拓展支持自定义的函数语法糖格式。比方说想要更好可读性的语法糖,只要实现了对应 DSL 字符串转化为 python 函数的解释器就可以了。

    第 1 条附言  ·  2021-08-08 19:06:26 +08:00

    不好意思,确实说得太抽象了,补充一些解释:

    from magic import dsl
    
    # 7 缩写的等价写法比较
    # 7.1 lambda 函数表达式
    lambda a: a.attr+1
    dsl | "a: a.attr+1"    
    # 原理就是很简单的加个`lambda `前缀就能 eval() 成函数使用
    dsl | "_.attr+1"   
    # 隐式参数 `_`,原理是默认添加前缀`lambda _:`
    dsl("a: a.attr+1")    
    # 可以用管道符也可以直接传参调用
    
    
    # 7.2 def 多行函数
    def func(a):
        b = a.attr + 1
        yield b
    
    def func(a): b = a.attr + 1; yield b
    # 简单的多行函数可以本来就可以用`;`分号直接缩写成一行
    
    dsl | "def(a): b=a.attr+1; yield b"    
    # 原理是将前缀`def`替换成`def func`就能 exec() 成函数使用
    dsl | "def a: b=a.attr+1; yield b"    
    # 有`def`前缀的话,省略的参数括号可以自动识别补全
    dsl | "(a): b=a.attr+1; yield b"    
    # 有`(a)`参数括号的话,省略的`def`前缀也可以自动识别补全
    dsl | "b=_.attr+1; yield b"    
    # 有`;`分号的话,会自动添加前缀和隐式参数`def func(_):`
    dsl("def(a): b=a.attr+1; yield b")    
    # 同样可以将管道符换成传参调用
    
    
    # 7.3 传递参数
    # 通常可以通过指定默认值来传递参数,如下
    lambda a=a,b=b: b or a+b 
    def func(a=a, b=b):   
        return b or a+b
    # 也可以绑定成偏函数partial来传递参数,如下
    import functools
    _func = lambda a,b=None: b or a+b
    func = functools.partial(_func, a, b=b)    
    # 多行函数也同理,如下
    def _func(a=a, b=b):    
        return b or a+b
    func = functools.partial(_func, a, b=b)
    
    dsl | ["a,b=None: b or a+b", a]    
    # 可以通过`["func", *args]`的形式传入位置参数生成偏函数
    dsl("a,b=None: b or a+b", a, b=b)    
    # 或者也可以通过调用的方式绑定位置参数和关键字参数
    
    # 8 最简缩写示例
    d = dsl
    func = d|"a: a.attr+1"
    

    可以看到原理很简单,使用方式也较为方便,主要目的是让函数表达式写起来更自由简短(省略lambda前缀)。所以想知道这么容易想到和实现的自定义伪语法糖,有没有类似的现成写好的库可以开箱即用呢?

    另外关于可读性问题,这个伪语法糖是为了减少简单函数表达式的长度,比如属性引用(lambda x:x.attr)、抽取切片(lambda x:x["key"])、函数调用(lambda x:x("args")),所以本来也不鼓励在伪语法糖里写又臭又长的函数表达式。

    18 条回复    2021-08-14 06:54:09 +08:00
    hsfzxjy
        1
    hsfzxjy  
       2021-08-08 12:44:29 +08:00 via Android   ❤️ 1
    我实现过一个。函数体不用写在字符串里,比你描述的更容易使用。https://github.com/hsfzxjy/lambdex
    gstqc
        2
    gstqc  
       2021-08-08 14:16:09 +08:00   ❤️ 1
    代码除了实现功能以外,还有很重要一点就是要给别人看的
    lichdkimba
        3
    lichdkimba  
       2021-08-08 17:30:53 +08:00
    这看不懂啊。。。。。
    efaun
        4
    efaun  
       2021-08-08 17:52:07 +08:00
    @gstqc #2 python3.9 又加了几个语法糖,真的学不动了……
    raaaaaar
        5
    raaaaaar  
       2021-08-08 18:09:02 +08:00 via Android
    语法糖太多的确看着难受,当然是因为我不太熟 py
    O5oz6z3
        6
    O5oz6z3  
    OP
       2021-08-08 19:15:29 +08:00
    @gstqc @lichdkimba @efaun @raaaaaar
    不好意思,我写得太抽象了,补充了一些描述在附言里。

    @raaaaaar #5
    这不是你的问题,是我的问题,python 没有这些语法糖。只是我自己想到了一个很简单就可以实现的伪语法糖,想问问大家意见。
    O5oz6z3
        7
    O5oz6z3  
    OP
       2021-08-08 19:28:26 +08:00
    @hsfzxjy #1
    确实大开眼界,语法很自由可读,设计很完善,完成度非常高,大受震撼……不过话说回来,我的目标是缩写 lambda,你的目标是强化 lambda,方向不一样。
    hsfzxjy
        8
    hsfzxjy  
       2021-08-08 19:38:13 +08:00   ❤️ 1
    @O5oz6z3 #7 抱歉,下午只看到了第二点。我知道有一些库能近似达到部分要求,如 https://github.com/dry-python/lambdas https://github.com/abersheeran/cool 。不过这类库都不会选择把函数体写在字符串里,那样可读性太差了,大家倾向于用现有语法组装成 DSL 。
    LeeReamond
        9
    LeeReamond  
       2021-08-08 19:48:14 +08:00
    并不好用,并不好读,并不 magic
    O5oz6z3
        10
    O5oz6z3  
    OP
       2021-08-08 20:06:17 +08:00
    @hsfzxjy #8 谢谢!
    说实话,这个帖子的想法正是来自于实现管道语法糖,整件事颇有点函数式尾递归的味道,其中遇到了需要经常定义 lambda 的问题,然后就想到了这个伪语法糖,想抛砖引玉一下。

    先不说我水平有限无法完整实现 DSL,要我用现成的语法组成 DSL 就变成 ORM 了,所以偷懒直接沿用 python 自己的语法。

    @LeeReamond #9 确实!所以想参考大家意见……
    ztcaoll222
        11
    ztcaoll222  
       2021-08-08 20:45:34 +08:00
    [格林斯潘第十定律] 。。。
    ClericPy
        12
    ClericPy  
       2021-08-08 22:37:21 +08:00
    需求描述的比抽象语法树还抽象... 一点都没感觉带来简便啊
    KAAAsS
        13
    KAAAsS  
       2021-08-08 23:14:38 +08:00
    没高亮的话行内代码看着有点头疼……
    而且我感觉其实也不是一定强求单行吧?适度的换行也是可以接受的,比如:
    ```haskell
    func x = case x of
    1 -> 2
    2 -> 3
    otherwise -> 4
    ```
    O5oz6z3
        14
    O5oz6z3  
    OP
       2021-08-09 00:37:53 +08:00
    @ztcaoll222 #11 不懂 lisp,所以不知道指的是哪一点像……

    @ClericPy #12 比 AST 还抽象那是不敢当……不过我的确也说不明白,大概是三个问题的混合:
    1. lambda 前缀写起来有点长,有没有什么库提供更简便的生成函数表达式的方法。
    2. 有关 python 的 DSL 库,有没有什么值得推荐的库。
    3. 针对第一点,想到一个“简单”的解决思路,这个思路如何、是不是已经有人实现过了。

    不过我的“解决思路”也描述得很抽象,所以讨论也无从谈起……

    @KAAAsS #13 你说的代码高亮确实是个问题。强求单行则是为了能在表达式里塞下函数定义,比方说 map() 需要的映射函数,有了匿名函数就可以直接在定义在表达式里`map((lambda a: a+2), list)`,如果用换行的方式就不太方便了。
    1018ji
        15
    1018ji  
       2021-08-09 18:03:22 +08:00
    看不懂
    O5oz6z3
        16
    O5oz6z3  
    OP
       2021-08-09 21:50:47 +08:00
    @1018ji #15 是我说复杂了,简单来说的话就是:“把函数定义放在字符串里,定义的时候就可以省略关键字 lambda 、def 和函数名。这样的语法糖设计大家觉得实用吗?”(虽然这样的设计实现起来很简单,但是没有了语法高亮和可读性)
    abersheeran
        17
    abersheeran  
       2021-08-13 15:18:19 +08:00
    不如把整个文件作为一个 Python 超集进行解析,转成 Python ast 跑。
    O5oz6z3
        18
    O5oz6z3  
    OP
       2021-08-14 06:54:09 +08:00
    @abersheeran #17 超集的确会语法更灵活,但实现难度也高很多……

    顺带一提找到了一个库 ideas 似乎能较简单地自定义语法糖,比如替换 lambda 关键字:github.com/aroberge/ideas
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2927 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 15:10 · PVG 23:10 · LAX 08:10 · JFK 11:10
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.