I've often wanted an anonymous function that would do more than return the result of an expression. So I developed one around the exec function that supports assignments and multiline lambdas but suspect a few things are missing. Suggestions would be appreciated, please.
I've tried to keep the syntax similar to a normal lambda and also easy to debug by copying the text into a standard function.
Tested with CPython 3.6.5 and Pypy 3.5.3. To give an idea of the current version, it is capable of evaluating these functions:
λ("""var:
for i in range(var):
print(i, end = ",")
print(' ', end='')
if i == var - 1:
print(var)
print(var)
rtrn = [i for i in range(var, 0, -1)]
""")(5)
0, 1, 2, 3, 4, 5
5
[5, 4, 3, 2, 1]
λ("x: [x*i for i in range(x)]; print('Final:', rtrn[-1])")(3)
Output: 6
[0, 3, 6]
λ("x, y:: from math import cos, sin; rtrn = cos(x) + sin(y)")(3, 5)
-1.9489167712635838
def lambda_(code):
"""
Anonymous function supporting multiple line and assignments.
For one line the form is λ("parameters : code incl assignment")(*arguments)
and the first exepression is assigned to rtrn which is returned
For multiline, use triple quotes & the parameters must be on the first line,
the : is followed directly by a line feed and then the code
Input:
code in the form "x, y,...: return_value_expression; other code"
Output is assigned to variable rtrn
"""
def build_lambda(*args):
"""
All arguments are declared global as listcomps etc don't create closure
when called using exec, so declare parameters & rtrn as globals
Multiline will return None unless you assign rtrn a value.
"""
val_idx, val_name = 0, []
i, parameters = -1, []
if ":" in code:
for i, c in enumerate(code): # loop until the : that ends parameters
if c in ",:":
if val_name:
parameters.append(''.join(val_name))
val_name = []
val_idx += 1
if c == ":":
break
elif c != " ":
val_name.append(c)
# exec_code sets rtrn = None for multiline or sets rtrn = first expression for
# single line unless :: (used if first expression can't be evaluated)
if code[i+1] != "\n": # single line,
if code[i+1] != ":":
exec_code = ''.join(("rtrn = ", code[i+1:]))
else:
exec_code = ''.join(("rtrn = None; ", code[i+2:]))
else: # multiline
# wont run if excess leading spaces so remove them
lead = 0
code_ = code[i+1:]
while code_[lead+1] == " ": # how many on 1st line?
lead += 1
for i in range(1, len(code_)): # remove that on all lines
if code_[i-1: i+1] == "\n ":
code_ = code_[: i] + code_[i+lead:]
exec_code = "rtrn = None" + "\n" + code_ # executable code
define_globals = ', '.join(["global rtrn"] + parameters)
assign = [' = '.join((p, str(args[i]))) for i, p in enumerate(parameters)]
assign = '; '.join(assign)
cmd = '; '.join((define_globals, assign if assign else "pass", exec_code))
if args and args[-1] == "print":
print(cmd, "\n")
exec(cmd)
return rtrn
return build_lambda
λ = lambda_ # rebind to linux ctrl+shift+u 03BB
With the added complexity it seems more reasonable to create a class splitting code generation, execution and printing. The code is posted in this question.
-
\$\begingroup\$ I fail to see any advantages over a local function definition. What is the problem at hand this implementation is trying to solve? \$\endgroup\$301_Moved_Permanently– 301_Moved_Permanently2018年05月19日 17:52:59 +00:00Commented May 19, 2018 at 17:52
-
\$\begingroup\$ You share that opinion with the BDFL who I understand may have even wanted lambda's removed from Python 3. The purpose of this implementation is to explore the use of a less limited anonymous function. I do recognize the issues including the use of exec. I posted here because I understand that this is a good place to seek feedback, which I have received :) \$\endgroup\$John 9631– John 96312018年05月20日 00:01:26 +00:00Commented May 20, 2018 at 0:01
-
1\$\begingroup\$ You should probably post the new version of the software as a new question, then revert this and add links between the questions. That way the feedback for both versions is timely and you're more likely to get feedback on the new version. \$\endgroup\$l0b0– l0b02018年05月20日 00:25:19 +00:00Commented May 20, 2018 at 0:25
1 Answer 1
Naming:
λ
is not a standard Python function name ([a-z]
followed by[a-z0-9_]+
) and will require figuring out some magic sequence in non-Greek keyboard layouts.s0
,s1
,s2
,lead
andcd
should be renamed to be more intuitive. For example, based on your documentations1
could be named something likeassignments
.
General:
- Rather than adding strings and newlines together I'd use
format()
andjoin()
. It's easier to follow the construction of the string that way. - I would not recommend using this in production code. Treating strings as code is going to result in many more runtime errors which will be harder to debug than the corresponding named function.
λ
does not accept functions without parameters -λ('print("value")')()
for example results inIndexError
.- Code construction and execution should be split up for easier debugging. I should be able to run
construct_code(my_code_string)
and see what would be executed for debugging. - I don't have the time to test this myself, but you should be able to get rid of the
global rtrn
by constructing an actual function and running that instead of creating global code.
-
\$\begingroup\$ Thanks very much for the comments, @l0b0. I'll review & adjust tomorrow On the last point I still need globals for the parameters for access to list comps but I'll review rtrn. \$\endgroup\$John 9631– John 96312018年05月19日 10:12:43 +00:00Commented May 19, 2018 at 10:12
-
\$\begingroup\$ I updated it to reflect your suggestions. I've used a more general name and bound it to λ for those who prefer. I agree with your concern about using exec in production code. Originally I assumed that in a no parameter situation, like a normal lambda, we would have a : however I've tested for users not including the colon in this version. I don't think you can get rid of the global return but will have another look when I have enough time. \$\endgroup\$John 9631– John 96312018年05月19日 23:35:40 +00:00Commented May 19, 2018 at 23:35
-
\$\begingroup\$ I added the ability to add the string "print" to the end of the arguments to print the code but at that point I felt a class might be more appropriate so I include that (and extra tests) below. \$\endgroup\$John 9631– John 96312018年05月19日 23:36:02 +00:00Commented May 19, 2018 at 23:36