Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

lambda function accessing outside variable [duplicate]

Tags:

python

scope

I wanted to play around with anonymous functions so I decided to make a simple prime finder. Here it is:

tests = []
end = int(1e2)
i = 3
while i <= end:
    a = map(lambda f:f(i),tests)
    if True not in a:
        tests.append(lambda x:x%i==0)
        print i
    print tests
    print "Test: "+str(i)
    print str(a)
    i+=2

What I find however, is that the i in the lambda x:x%i==0 is accessed each time, while i want it to be a literal number. how can I get it to become lambda x:x%3==0 instead?

like image 962
Hovestar Avatar asked Jan 10 '14 20:01

Hovestar


People also ask

Is it possible to access variables from the outside of a lambda expression?

The rule is that a lambda expression can only access local variables from an enclosing scope that are effectively final.

How are variables used outside of lambda expression?

Because the local variables declared outside the lambda expression can be final or effectively final. The rule of final or effectively final is also applicable for method parameters and exception parameters. The this and super references inside a lambda expression body are the same as their enclosing scope.

Can lambda function access global variables?

Yes, sure. Normal name lookup rules apply.

Can lambda Access outer variable in Java?

Lambda expressions can use variables defined in an outer scope. We refer to these lambdas as capturing lambdas. They can capture static variables, instance variables, and local variables, but only local variables must be final or effectively final.

How to assign a value to a local variable outside lambda expression?

Inside lambda expression, we can't assign any value to some local variable declared outside the lambda expression. Because the local variables declared outside the lambda expression can be final or effectively final.

What is an example of a lambda expression?

For example, a lambda expression can use an instance or static variable defined by its enclosing class. A lambda expression also has access to (both explicitly and implicitly), which refers to the invoking instance of the lambda expression’s enclosing class.

What is a variable capture in lambda expressions?

However, when a lambda expression uses a local variable from its enclosing scope, a special situation is created that is referred to as a variable capture. In this case, a lambda expression may only use local variables that are effectively final. An effectively final variable is one whose value does not change after it is first assigned.

What is a Lambda environment variable?

An environment variable is a pair of strings that are stored in a function's version-specific configuration. The Lambda runtime makes environment variables available to your code and sets additional environment variables that contain information about the function and invocation request.


3 Answers

You can "capture" the i when creating the lambda

lambda x, i=i: x%i==0

This will set the i in the lambda's context equal to whatever i was when it was created. you could also say lambda x, n=i: x%n==0 if you wanted. It's not exactly capture, but it gets you what you need.


It's an issue of lookup that's analogous to the following with defined functions:

i = "original"

def print_i1():
    print(i) # prints "changed" when called below

def print_i2(s=i): # default set at function creation, not call
    print(s) # prints "original" when called below


i = "changed"
print_i1()
print_i2()
like image 60
Ryan Haining Avatar answered Oct 19 '22 22:10

Ryan Haining


The problem is that each of those functions in tests is referring to the variable i.

More commonly, you do this inside a function, in which case you have a local-to-the-defining-scope variable i, which gets stored in a closure, as nicely explained in These Nasty Closures.

But here, it's even simpler: i is a global variable, so there is no closure. The functions are compiled to look up i as a global variable when run. Since i has changed, the functions will see the changed value when they run. Simple as that.


The traditional way around this (which works for both closures and globals) is fondly known as "the default-value hack", even though it's not really a hack. (See the explanation in the FAQ.) Ryan Haining's answer explains how to do this:

lambda x, i=i: x%i==0

This creates a parameter named i, with a default value equal to the value of i at the time the function is created. Then, inside the function, when you access parameter i, and you get that value.


A different way around this, which may seem more familiar if you're using to languages like JavaScript, is to create a function-creating function, and pass the value of i as an argument to that function-creating function, as in user2864740's answer:

(lambda i: lambda x: x%i)(i)

This avoids "polluting" the signature of the function with an extra parameter (that someone could accidentally pass an argument to), but at the cost of creating and calling a function for no good reason.


A third way around this is to use partial. In cases where all you're trying to do is partially apply a function, using partial instead of defining a wrapper function as a lambda can be cleaner.

Unfortunately, in this case, the function is hidden inside an operator, and the function operator.mod that exposes it doesn't take keyword arguments, so you can't usefully partial its second operand. So, this is a bad solution in this case. If you really wanted to, you could just write a wrapper that behaves better and partial that:

def opmod(a, b):
    return a % b

partial(operator.mod, b=i)

In this case, I think you're better off with the other solutions; just keep this one in your head for cases where it is appropriate.

like image 8
abarnert Avatar answered Oct 19 '22 23:10

abarnert


Create a new function that returns the lambda. Then call that, passing in i as an argument. This will create a new binding scope.

def make_test (i):
   # this i refers to the parameter (which evaluates to the /value/ passed)
   return lambda x: x%i==0

# ..
# the /value/ resulting from evaluating the variable is passed
tests.append(make_test(i))
like image 2
user2864740 Avatar answered Oct 20 '22 00:10

user2864740