0

I am using setattr() to monkey patch a closure. Using getattr() shows that the closure was patched correctly. However, the return value is unaffected.

>>> def foo():
...     def bar():
...             return('bar')
...     return(bar())
... 
>>> def baz():
...     return('baz')
... 
>>> setattr(foo, 'bar', baz)
>>> bar = getattr(foo, 'bar')
>>> bar()
'baz'
>>> foo()
'bar'

After using setattr() I expect foo() to return 'baz'; but, it returns 'bar' as if the patch never happened.

  • 4
    I don't think `bar` is an attribute of `foo`; it's just a nested function. You would probably have to do something like externally reassign a variable that `bar` uses. – Carcigenicate Sep 12 '19 at 16:33
  • `setattr(foo, 'bar', baz)` creates an attribute for `foo` - https://stackoverflow.com/questions/338101/python-function-attributes-uses-and-abuses – wwii Sep 12 '19 at 16:37
  • Thanks! Yes this seems to be the answer. Using `getattr(foo, 'bar')` before the `setattr()` throws an `AttributeError`. So `foo` does not have an attribute `bar` at first. Using `setattr()` of course gives it one. – James Rowland Sep 12 '19 at 16:40

1 Answers1

1

It did work, just not as you're expecting:

f = foo.bar

print(f())  # baz

bar is a nested function inside foo; not an attribute of it. As far as I know, there is no way of reassigning an inner function like this.

Your best best is probably to have an externally-accessible variable that foo relies on, and reassign that as needed. Or give it a mutable object that you mutate externally into a desired state. I can't say I'd recommend these options, but given the current requirements, they should work.

Carcigenicate
  • 43,494
  • 9
  • 68
  • 117
  • This is a bummer. There are times when nested functions make for an elegant solution. But, they are difficult to handle during testing, e.g., I may want dependency injection for `bar`. The `bar` method must be bound to `foo` somehow. But is it patchable? – James Rowland Sep 12 '19 at 16:48
  • @JamesRowland I just started my shift unfortunately. I'll look into this when I get home. I don't think there's a way without manually setting up a mechanism before-hand though. – Carcigenicate Sep 12 '19 at 17:07