0

given the following snippet:

def fun(ret):
    return ret

class A:
    def __init__(self):
        for string in ['a', 'b']:
            setattr(self, string, lambda: fun(string))

>>> a = A()
>>> a.a()
'b'

I want a method a() which returns 'a' and a method b() which returns 'b'. As long as I don't use a lambda expression but setting the attribute to a simple string, the association is correct.

I think my intention is clear? So where am I wrong?

2
  • 1
    See e.g. stackoverflow.com/q/7514093/3001761 and the various linked questions. Commented May 28, 2018 at 16:40
  • What jon said. The link shows how to fix it using a default arg. Another thing you can do to illustrate what's going on is to set string after the loop, eg string='hello'. BTW, string isn't a great variable name choice, since it's the name of a standard module. Commented May 28, 2018 at 16:48

1 Answer 1

1

In Python, a function will lookup non-local names in the scope where it was defined or in the global scope if the name still does not exist there. If the value associated to the name changed, so will the returned value. Note that this is not specific to lambda functions.

A way around this is to create a closure by writing a helper function.

def fun(ret):
    return ret

class A:
    def __init__(self):
        def apply_fun(item):
            return lambda: fun(item)

        for string in ['a', 'b']:
            setattr(self, string, apply_fun(string))

print(A().a())  # 'a'

Alternative solution

In that particular case, using __getattr__ might be more suited as it is intended to dynamically return attributes.

def fun(ret):
    return ret

class A:
    def __getattr__(self, item):
        if item in ['a', 'b']:
            return lambda: fun(item)

print(A().a())  # 'a'
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.