1

I'm working on an add-on for Blender, and I'm trying to assign a custom update function to a list of properties through an "update" attribute. The update function will only accept 2 parameters (self and context, or s and c). But I want to send a third parameter that identifies which property is being updated. This parameter is an index. This is what I started with:

for i in range(0,len(props)):
    props[i].update = lambda s, c: CustomUpdate( s, c, i )

But I soon realized that lambda records the variable being used, rather than the value of that variable. So all of the property update functions end up being generated like this:

for i in range(0,len(props)):
    props[i].update = CustomUpdate( s, c, len(props) - 1 )

I looked for answers and found this solution:

for i in range(0,len(props)):
    props[i].update = lambda s, c, index=i: CustomUpdate( s, c, index )

However, Blender appears to be double checking the number of parameters for the function supplied here, and throws an error when more than 2 are used, so I can't use a third lambda parameter.

So I'm currently trying to figure out how to convince the lambda to generate a unique index for each property for the callback. Possibly some way to edit the lambda after assigning it? Or any trick to wrap i in some code to force evaluation during parsing instead of during execution?

Edit: Forgot to mention that my list of props is static. So the loop index counter could be unrolled by the parser, if such a thing is possible.

Robert
  • 413
  • 4
  • 12
  • I wish someone would make a simpler version of this problem/answer for the less initiated. I doubt most new programmers will even know what a closure is. It will be difficult for them to locate that page when they run into this problem. – Robert Oct 30 '19 at 13:24

1 Answers1

2

Each lambda needs its own context to hold its value of i:

def makeLambda(i):
    return lambda s,c: CustomUpdate( s, c, i )
for i in range(0,len(props)):
    prop[i].update = makeLambda(i)
Scott Hunter
  • 48,888
  • 12
  • 60
  • 101
  • Why doesn't lambda make it's own context? I would have expected it to make a closure 'recording' the values of `i`. – Dan Oct 30 '19 at 13:12
  • I guess this answers my question: https://stackoverflow.com/a/2295368/1011724. Interesting. – Dan Oct 30 '19 at 13:15
  • There's nothing to close over; the `for` loop doesn't define a new local scope. – chepner Oct 30 '19 at 13:16
  • I just tested it. It works. You have saved my week. Thanks man – Robert Oct 30 '19 at 13:16
  • 1
    (Or put another way, it's a "closure" over the global scope, but the `for` loop continuously changes the value of `i` in that scope.) – chepner Oct 30 '19 at 13:16
  • Another (similar) approach would be `prop[i].update = partial(lambda s,c: CustomUpdate( s, c, i ), i)` – Dan Oct 30 '19 at 13:20
  • 1
    @Dan Not quite; the `i` in the lambda is still a free variable; all you've done is created a callable which uses the current value of `i` as the `s` argument for `CustomUpdate`. – chepner Oct 30 '19 at 13:28
  • 1
    @chepner Oops, typo `prop[i].update = partial(lambda i, s, c: CustomUpdate(s, c, i ), i)` – Dan Oct 30 '19 at 13:31