# A less limited Python Lambda

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
code_ = code[i+1:]
while code_[lead+1] == " ":  # how many on 1st line?
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.

• I fail to see any advantages over a local function definition. What is the problem at hand this implementation is trying to solve? May 19 '18 at 17:52
• 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 :) May 20 '18 at 0:01
• 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.
– l0b0
May 20 '18 at 0:25

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 and cd should be renamed to be more intuitive. For example, based on your documentation s1 could be named something like assignments.

General:

• Rather than adding strings and newlines together I'd use format() and join(). 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 in IndexError.
• 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.
• 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. May 19 '18 at 10:12
• 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. May 19 '18 at 23:35
• 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. May 19 '18 at 23:36