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 函数的解释器就可以了。
不好意思,确实说得太抽象了,补充一些解释:
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"))
,所以本来也不鼓励在伪语法糖里写又臭又长的函数表达式。
1
hsfzxjy 2021-08-08 12:44:29 +08:00 via Android 1
我实现过一个。函数体不用写在字符串里,比你描述的更容易使用。https://github.com/hsfzxjy/lambdex
|
2
0ZXYDDu796nVCFxq 2021-08-08 14:16:09 +08:00 1
代码除了实现功能以外,还有很重要一点就是要给别人看的
|
3
lichdkimba 2021-08-08 17:30:53 +08:00
这看不懂啊。。。。。
|
5
raaaaaar 2021-08-08 18:09:02 +08:00 via Android
语法糖太多的确看着难受,当然是因为我不太熟 py
|
6
O5oz6z3 OP @gstqc @lichdkimba @efaun @raaaaaar
不好意思,我写得太抽象了,补充了一些描述在附言里。 @raaaaaar #5 这不是你的问题,是我的问题,python 没有这些语法糖。只是我自己想到了一个很简单就可以实现的伪语法糖,想问问大家意见。 |
7
O5oz6z3 OP @hsfzxjy #1
确实大开眼界,语法很自由可读,设计很完善,完成度非常高,大受震撼……不过话说回来,我的目标是缩写 lambda,你的目标是强化 lambda,方向不一样。 |
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 。
|
9
LeeReamond 2021-08-08 19:48:14 +08:00
并不好用,并不好读,并不 magic
|
10
O5oz6z3 OP @hsfzxjy #8 谢谢!
说实话,这个帖子的想法正是来自于实现管道语法糖,整件事颇有点函数式尾递归的味道,其中遇到了需要经常定义 lambda 的问题,然后就想到了这个伪语法糖,想抛砖引玉一下。 先不说我水平有限无法完整实现 DSL,要我用现成的语法组成 DSL 就变成 ORM 了,所以偷懒直接沿用 python 自己的语法。 @LeeReamond #9 确实!所以想参考大家意见…… |
11
ztcaoll222 2021-08-08 20:45:34 +08:00
[格林斯潘第十定律] 。。。
|
12
ClericPy 2021-08-08 22:37:21 +08:00
需求描述的比抽象语法树还抽象... 一点都没感觉带来简便啊
|
13
KAAAsS 2021-08-08 23:14:38 +08:00
没高亮的话行内代码看着有点头疼……
而且我感觉其实也不是一定强求单行吧?适度的换行也是可以接受的,比如: ```haskell func x = case x of 1 -> 2 2 -> 3 otherwise -> 4 ``` |
14
O5oz6z3 OP @ztcaoll222 #11 不懂 lisp,所以不知道指的是哪一点像……
@ClericPy #12 比 AST 还抽象那是不敢当……不过我的确也说不明白,大概是三个问题的混合: 1. lambda 前缀写起来有点长,有没有什么库提供更简便的生成函数表达式的方法。 2. 有关 python 的 DSL 库,有没有什么值得推荐的库。 3. 针对第一点,想到一个“简单”的解决思路,这个思路如何、是不是已经有人实现过了。 不过我的“解决思路”也描述得很抽象,所以讨论也无从谈起…… @KAAAsS #13 你说的代码高亮确实是个问题。强求单行则是为了能在表达式里塞下函数定义,比方说 map() 需要的映射函数,有了匿名函数就可以直接在定义在表达式里`map((lambda a: a+2), list)`,如果用换行的方式就不太方便了。 |
15
1018ji 2021-08-09 18:03:22 +08:00
看不懂
|
16
O5oz6z3 OP @1018ji #15 是我说复杂了,简单来说的话就是:“把函数定义放在字符串里,定义的时候就可以省略关键字 lambda 、def 和函数名。这样的语法糖设计大家觉得实用吗?”(虽然这样的设计实现起来很简单,但是没有了语法高亮和可读性)
|
17
abersheeran 2021-08-13 15:18:19 +08:00
不如把整个文件作为一个 Python 超集进行解析,转成 Python ast 跑。
|
18
O5oz6z3 OP @abersheeran #17 超集的确会语法更灵活,但实现难度也高很多……
顺带一提找到了一个库 ideas 似乎能较简单地自定义语法糖,比如替换 lambda 关键字:github.com/aroberge/ideas |