Yagiz Degirmenci
Yagiz Degirmenci

Reputation: 20726

Python: Using lambda inside lambda returns lambda object

I was reading a snippet that uses lambda inside a lambda then i wanted to test it out by creating a dummy function that reads from a file then returns the maximum and minimum number.

This is what I came up with

dummy = lambda path: (lambda x, y: (lambda data: ([y for x in open(path, "r").readline().splitlines() for y in x])[0])(min(data), max(data)))

With a little bit of formatting, this is how it looks(IMHO one liner is much more readable.)

dummy = lambda path: (
    lambda x, y: (
        lambda data: ([y for x in open(path, "r").readline().splitlines() for y in x])[
            0
        ]
    )(min(data), max(data))
)

But it always returns the lambda object

Out: <function <lambda>.<locals>.<lambda> at 0x7fe26f00a4c0>

or this

'function' object is not subscriptable; perhaps you missed a comma?

What am i doing wrong?

Upvotes: 3

Views: 229

Answers (2)

pho
pho

Reputation: 25489

dummy = lambda path: (
    lambda x, y: (
        lambda data: ([y for x in open(path, "r").readline().splitlines() for y in x])[
            0
        ]
    )(min(data), max(data))
)

Let's split this over a bunch of lines.

lamb1 = lambda data: ([y for x in open(path, "r").readline().splitlines() for y in x])[0] 

This doesn't look right -- why never use data?

Next:

lamb2 = lambda x, y: lamb1(min(data), max(data))

lamb2 calls lamb1 with 2 arguments -- min and max of data. Where'd data come from? And why are we passing two arguments to a lambda that only wants one?

And finally:

dummy = lambda path: lamb2

So dummy is a lambda that when called, returns the lambda lamb2. You never call lamb2, so you can't expect it to return a value. If you did call lamb2, you'd get an error.

It's hard to think through and debug one-liners, so let's define this as a regular function first.

def dummy(path):
    lines = [int(x) for x in open(path, "r").readlines()]
    smallest = min(lines)
    largest = max(lines)
    return (smallest, largest)

Now to convert each line to a lambda:

readfile = lambda file: [int(x) for x in open(file, "r").readlines()]
minmax = lambda data: (min(data), max(data)
dummy = lambda path: minmax(readfile(path))

And joining these lambdas:

dummy = lambda path: (lambda data: (min(data), max(data)))((lambda file: [int(x) for x in open(file, "r").readlines()])(path))

If I set data.txt to

100
23
2349
20
21
1

and then ran this as

dummy("data.txt")

I get the output:

(1, 2349)

Note that we still have a bug - we're not doing proper cleanup. We open a file, but we never close it. That's usually not a problem due to the reference counting mechanics of the CPython implementation, but reference counting is an implementation detail, and other implementations don't do it.

To fix this problem, the best way is to use a with statement:

def dummy(path):
    with open(path, 'r') as f:
        lines = [int(x) for x in f.readlines()]
    smallest = min(lines)
    largest = max(lines)
    return (smallest, largest)

but there is, deliberately, no "with expression". Python isn't designed to encourage stuffing everything into a big one-liner expression.

If we want to write our big one-liner lambda and still do proper cleanup, the solution closest to the spirit of the problem is to define a helper:

def with_(resource, code):
    with resource as x:
        return code(x)

Then we can translate

with open(path, 'r') as f:
    lines = [int(x) for x in f.readlines()]

to

lines = with_(open(path, 'r'), lambda f: [int(x) for x in f.readlines()])

and incorporating that into our big one-liner,

dummy = lambda path: (lambda data: (min(data), max(data)))((lambda file: with_(open(path, 'r'), lambda f: [int(x) for x in f.readlines()]))(path))

While we're at it, we might as well iterate over f directly instead of building a list with readlines and then iterating over that:

dummy = lambda path: (lambda data: (min(data), max(data)))((lambda file: with_(open(path, 'r'), lambda f: [int(x) for x in f]))(path))

Upvotes: 4

CryptoFool
CryptoFool

Reputation: 23119

This version of your code runs each of the lambdas and prints the first line of the file given to it as a parameter.

The main thing I had to do is wrap your lambda declarations in another set of parentheses. It seems that what you thought was a set of parentheses that would cause the lambda to be called turns out to be taken as part of the body of the lambda. Adding a set of parens around the lambda solves this.

Then I had the problem where your parameter names didn't really make sense, so just to get it to do something, I put in literal values instead.

Finally, your innermost lambda needed to be called before its return value, a list containing the lines of the file, could be acted on with [0].

This prints the first line of the text file I'm giving it the path to:

dummy = lambda path: (
    (lambda x, y: (
        (lambda data: ([y for x in open(path, "r").readline().splitlines() for y in x]))(3)[0]
    ))(1, 2)
)

print(dummy("/tmp/data.txt"))

Result:

Test file - Line #1

Upvotes: 2

Related Questions