# Extension on Python Lambda capability

I asked for assistance creating a Python Lambda function capable of assignments and multi-line lambdas in this post. Following @l0b0's suggestions, I realized that it was better constructed as a class.

There is a security risk associated with eval or exec and input strings as well as an increased difficulty debugging code but I wanted a more capable lambda for functional experiments. This is probably unsuitable for production code.

Print will provide a code string that can be executed if you add the arguments.

Any suggestions for further improvements would be appreciated.

class lambda_():
"""
Creates an executable 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 with the parameters 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 __init__(self, code):
"""
self.parameters parses parameters (preceding the colon)
self.code is the code after the colon with extra
leading spaces removed from multiline lambdas
"""
self.parameters = []
val_idx = 0
val_name = []
i = 0
if ":" in code:
for i, c in enumerate(code):  # loop until the : that ends parameters
if c in ",:":
if val_name:
self.parameters.append(''.join(val_name))
val_name = []
val_idx += 1
if c == ":":
break
elif c != " ":
val_name.append(c)
# self.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] != ":":
if code[i] != ":":
self.code = ''.join(("rtrn = ", code))
else:
self.code = ''.join(("rtrn = ", code[i+1:]))
else:
self.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:]
self.code = "rtrn = None" + "\n" + code_  # executable code

def __call__(self, *args):
"""
All arguments are declared global as listcomps etc don't create closure
when called using exec, so first declare parameters & rtrn as globals,
then collect parameter=value pairs in assignments.
Multiline will return None unless you assign rtrn a value.
"""
define_globals = ', '.join(["global rtrn"] + self.parameters)
assign = '; '.join([' = '.join((p, str(args[i])))
for i, p in enumerate(self.parameters)])
exec('; '.join((define_globals, assign if assign else "pass", self.code)))
return rtrn

def __str__(self):
args = ["??" for _ in self.parameters]
define_globals = ', '.join(["global rtrn"] + self.parameters)
assign = '; '.join([' = '.join((p, str(args[i])))
for i, p in enumerate(self.parameters)])
return '; '.join((define_globals, assign if assign else "pass", self.code))

λ = lambda_  # rebind to linux ctrl+shift+u 03BB


Examples:

print(
λ("""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)
)

print(λ("x: [x*i for i in range(x)]; print('Final:', rtrn[-1])")(3))

print(λ("x, y:: from math import cos, sin; rtrn = cos(x) + sin(y)")(3, 5))

def applyToEach(L, f):
for i in range(len(L)):
L[i] = f(L[i])

testList = [1, -4, 8, -9]
apply_to_each(testList, λ("x: x if x >= 0 else -x"))
print(testList, "\n")

multiline = λ("""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)]
""")

print("Multiline lambda") ; print(multiline)

p_lambda = λ('print("value")')

print("parameterless lambda") ; print(p_lambda)


I highly recommend that you adopt some sort of testing framework for your tests instead of having a stand alone script. There are quite a few options out there. For instance, pytest. I would read up on how to write tests for pytest and in particular have a look at how to capture IO since a lot of your tests depend on it.

I would also recommend you give more descriptive names to your tests instead of, for example, test_print_1 and test_print_2 I would try to explain what the particular test does. So in the case of test_print_1 and test_print_2 what particular case is each one trying to break?

Finally, you should look at PEP 8 Style Guide, functions should have snake_case instead of camelCase. So, for example, applyToEach becomes apply_to_each.

Also, classes typically use CapWords convention, however, because you are trying to emulate the lambda keyword, using lambda_ is probably a reasonable choice.

• Thanks for the comments @Dair. I should relabel them. They're not rigorous tests, just something so that anyone wanting to try the code wouldn't have to do everything themselves. I use snake but the function you mention is from a famous Python MOOC and I just copied the form. I seem to recall PEP 8 permitted that but, I agree that snake's nicer: the Professors had clearly spent too much time with Scheme. I might change it anyway :) – John 9631 May 20 '18 at 4:02

I'll just leave this one behind for anyone interested in Python lambda variations (please ignore PEP8 violations etc, it's just for fun if anyone wants it).

def λλ(s):
"""
Anonymous function:  λ("args: code incl assignment")(vals *t)
Input:
s = "parameters : code bound to z"
parameters are single characters separated by ' ' or ','
a variable length argument *t (for tuple)
parameter names bound to the args t[0], t[1], ...
remaining elements of the args go into t
Output is assigned to variable z
"""
def build_lambda(*t, cpy=[0]):
params = [c for c in s if c not in ', ' and (cpy if c != ':' else cpy.pop())]
assi = [c + '=' + str(t[i: i+1][0]) for i, c in enumerate(params)]
assigns = "; ".join(assi + [''.join(('t=t[', str(len(assi)), ':]'))])
globls = ', '.join(("global z", *params))
exec('; '.join((globls, assigns, ''.join(('z =', s[s.index(":")+1:])))))
return z
return build_lambda