最终结果

「可以用,但是丑」。

「丑归丑,但是可以用」。

;)

为什么要做这个

当然是因为之前撸出来的 Python 高阶函数的链式调用库啊!

如果不是很了解我在胡言乱语啥的朋友,可以参考一下这篇辣鸡小文

然后进入正题,由于在实际使用库的过程中(没错这个库我自己一直在使用(父爱.jpg)(doge.jpg)),强烈感受到单行 Lambda 的不便,于是心一横,就花了半小时又填了一个鸡肋功能坑,结果是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from fc import mml as m

# 普通操作
m('''
lambda x:
x*=2
return x
''')(1) # 1*2=2

# 带默认值的操作
m('''
lambda x,y=1:
x+=y
return x
''')(1) # 1+1=2

# 支持若干 list 和 dict 参数的操作
m('''
lambda *l,**k:
return (l,k)
''')(1, a=2) == ((1,), {'a': 2})

# 甚至支持递归的匿名函数(但是必须要使用 {self} 代替函数名)
assert Fc([1, 2, 3, 4, 5]).map(m('''
lambda x:
if x<=0: return 0
else: return x+{self}(x-1)
''')).done() == [1, 3, 6, 10, 15]

丑归丑,但是勉强可以用啊!(完了这个工程思维)

怎么做到了?

说来惭愧,因为没咋自学「编译原理」,所以不能做到像 Vue.js 一样完全编译一遍源代码,只能采取另外的手段——execeval 元编程俩兄弟来做的,逻辑很简单,先用 exec 跑一遍解析后的多行 lambda,再用 eval 把这个函数返回。

1
2
3
4
5
6
# 这个是例子
'''
lambda x,y=1:
x+=y
return x
'''

我们看着上面的例子,一步一步讲一遍

  1. 把字符串拿来解析一遍,把 headbody 部分用正则提出来,这里的 headlambda x,y=1:body 就是剩下的部分
  2. head 里的参数部分提出来,这里是 x,y=1,在这里,你有没有发现,这玩意儿很像我们的 def xxxxxx(x,y=1) 这种模式?对了,这就是我们接下来的操作
  3. 把参数 x,y=1 拿出来,组合到 def xxxxxx(x,y=1) 字符串里,这里的 xxxxxx 是随机生成的 32 位字符串——这里是为了支持递归操作:为了摆脱闭包,所以使用了 globals() 来使动态生成的函数全局化,同时为了解决防止作用域冲突,所以使用了随机字符串的方式给 lambda 命名——这样还冲突的话我无话可说。。。(超低概率=不可能发生),其实这个看起来很奇怪的 {self} 命名,只是为了支持简单的 str.format(self="随机生成函数名") 罢了。。。
  4. head 有了 body 也有了,那么只需要把他们用字符串拼在一起,那就可以塞到 exec 里面了——即
1
2
3
def xxxxxx(x,y=1):
x+=y
return x

这么看来,就只剩下最后一步了,返回动态生成的 xxxxxx,怎么返回呢?因为函数名是字符串,那就要用到 eval 操作了

1
return eval("xxxxxx")

这么一来,动态生成的函数就这样返回了,然后再进一步当做匿名(32 位随机字符串名)的 lambda 用就行了~

最后说一句

很明显,这个方案有俩缺点

  1. 无法提供代码补全,不过由于一般来说函数体都会比较小(长的请丢到单独的函数里操作),所以也不算太大的问题
  2. 丑,没办法,要骂就骂独裁者 Guido van Rossum 不支持多行 Lambda 吧。。。

功能上的一个遗憾:没有实现递归操作,问题在作用域,因为是使用的闭包,所以无法将名字传出去——当然,可以在外面命一个相同的名字,但是这样未免限制过多,而且不如直接的写一个函数简单便捷,算是一个遗憾了吧。。。

好了除了丑就没有遗憾了!(感觉除了再加一层编译以外,没啥办法可以改变这个丑陋的缺点了,当然,欢迎和我讨论;)

;)

再最后说一句

代码在 ->这里<-

如果有啥好的建议可以发送邮件到 `A@Thoxvi.com` 我可是秒回小王子嘿嘿嘿

再再最后说一句

如果喜欢的话可以给个 Star 啊!嘿嘿嘿

再再再最后说一句

希望你可以使用它!然后喜欢上它!

祝玩得开心 ;)