4
\$\begingroup\$

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
            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:]
            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)
\$\endgroup\$
4
\$\begingroup\$

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.

\$\endgroup\$
1
  • \$\begingroup\$ 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 :) \$\endgroup\$ – John 9631 May 20 '18 at 4:02
0
\$\begingroup\$


Thanks for the comments.

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
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.