0

Consider the following example:

test = 123
f = lambda x,test=test: print(x, test)
del test
f('hello')

prints

hello 123

By capturing the variable in the lambda definition, the original variable seems to be retained.

Is the lambda ok to use when you could also use a simple object to store the same data?

class Test:
    def __init__(self, some_data):
        self.some_data = some_data

    def f(self, x):
        print(x, self.some_data)

t = Test('123')
t.f('hello')
12
  • 3
    Please don't ask 2 separate questions at the same time. See Accessing the default argument values in Python. Commented Oct 8, 2018 at 9:38
  • 2
    You should never name your lambda functions, just use a full function definition instead. Indeed, what you are seeing applies to any function with a default argument. Anyway, default variables can be thought of like attributes to function objects, but I think the use-cases aren't perfectly overlapping Commented Oct 8, 2018 at 9:43
  • 2
    Quoting PEP8-E731: "do not assign a lambda expression, use a def" Commented Oct 8, 2018 at 9:43
  • 1
    Both are ok to use. A lambda is much shorter and expresses the intention (to just have a callable) better. Commented Oct 8, 2018 at 9:44
  • 1
    Both examples look contrived. What's the point of doing this instead of just defining a normal function with two arguents (or one if that is all you need)? Commented Oct 8, 2018 at 9:56

2 Answers 2

2

By capturing the variable in the lambda definition, the original variable seems to be retained.

Becaused you made it the default value for the test argument of the lambda, and Python only evaluates arguments defaults once when the function is created. FWIW, this has nothing to do with the use of the lambda keyword - lambda is just syntactic sugar and you'd get exactly the same result with a "full-blown" function, ie:

test = 123
def f(x, test=test):
    print(x, test)
test = 456
f('hello')

Is the lambda ok to use when you could also use a simple object to store the same data?

Oranges and apples, really. A function is not meant to be use to "store" anything, but to perform some computation. The fact that it can capture some values either via the arguments defaults or via a closure, while quite useful for some use cases, is not meant as a replacement for proper objects or collections. So the answer is : depends on what you want to do with those values and your function.

NB : technically what you're doing here is (almost) what is known as "partial application" (you'd just have to rename Test.f to Test.__call__ and use t("hello") in the second example)). A partial application of a function is the mechanism by which a function of N arguments, when called with N - x (with x < N) arguments, returns a function of N-x arguments that when called with the missing arguments will return the result of the original function, ie:

def foo(a, b=None):
   if b is None:
       return lambda b, a=a: foo(b, a)
   return a + b

# here, `f` is a partial application of function `foo` to `1`
f = foo(1)
print(f)
f(2)

In this case we use a closure to capture a, in the functional programming tradition - "partial application" is also mostly a functionnal programming concept FWIW. Now while it does support some FP features and idioms, Python is first and foremost an OO language, and since closures are the FP equivalent of objects (closures are a way to encapsulate state and behaviour together), it also makes sense to implement partial application as proper class, either with "ad hoc" specialized objects (your Test class but also Method objects), or with a more generic "partial" class - which already exists in the stdlib as functools.partial

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks, I wasn't aware the capturing worked with a regular function, too.
@Stefan lambda creates "regular" functions, it's just a shortcut for when you don't really need a named function (typically: one-shot callbacks).
In my use case the lambda would be "loaded with data" and then returned by another function.
Once again it depends on how it's used. If it's a "one-shot" special case function only used in one single place then a closure (lambda or "full" function) makes sense. If it represents some entity in your program, is used in more than one single specific place, has any other responsabily, etc, then a proper class is probably a better choice - IOW it's really a question of context more than a purely technical one.
1

The default argument value of an argument is evaluated once, when the function or lambda is defined. It is not evaluated at each call site. (This is why you usually cannot use [] as a default argument and have to specify None instead, and write code for allocating a fresh list each time the function is called.)

It really depends on the surrounding code if a lambda, function, or class is appropriate. Usually, if there is only one operation and the state cannot be mutated directly from the outside (only within the operation), a lambda or function is appropriate.

3 Comments

s/usually cannot/usually should not/ - you can use mutable default arguments (it is technically legal, and even have a couple valid use cases).
Well, usually it does not work as the programmer intended …
It's indeed one of the most infamous Python gotchas, but saying that you "can not" use an empty list/dict/empty container/mutable object as argument default is just technically wrong - you can do it ;-)

Your Answer

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.