5
\$\begingroup\$

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.

asked May 19, 2018 at 4:12
\$\endgroup\$
3
  • \$\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\$ Commented 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\$ Commented 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\$ Commented May 20, 2018 at 0:25

1 Answer 1

5
\$\begingroup\$

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.
answered May 19, 2018 at 9:41
\$\endgroup\$
3
  • \$\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\$ Commented 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\$ Commented 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\$ Commented May 19, 2018 at 23:36

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.