Question

I have this hobbyist pubsub library I put together that uses extensive weakref but I recently noticed that lambdas appear to be very similar in their execution, save some differences which strike me as... counter-intuitive. (if only because I'm not sure what lambdas are actually doing)

Take the following class as an example of an object that might want to pub-sub:

>>> class A(object):
...     def __init__(self, x):
...             self.x = x
...     def foo(self):
...             self.x += 1
...     def bar(self):
...             return "my x is %d" % self.x
... 
>>> Stick = A(15)
>>> Stick.x
15
>>> Stick.foo()
>>> Stick.bar()
'my x is 16'

Without importing the weakref library and hacking away at a custom weak reference to bound methods, one could use lambda to do something similar.

this = lambda: Stick.foo
that = lambda: Stick.bar

Using sys.getrefcount before and after this and that are created will showcase that there is no change to the reference count for either the main object or its bound methods.

>>> sys.getrefcount(Stick)
2
>>> sys.getrefcount(Stick.foo)
1
>>> sys.getrefcount(Stick.bar)
1

Furthermore, the 'not-weakrefs' can be placed in a container, again without altering the refcount for the object or its bound methods.

events = [this, that]

Finally the 'not-weakref' lambdas can be called with double-parentheses. Looks a little ugly, but so do some of the 'weak bound method' hacks out there (including my own), so this is passable IMHO.

>>> for e in events:
...     e()()
... 
'my x is 17'
>>> 

Seems okay -- until the main object is deleted.

>>> del Stick
>>> for e in events:
...     e()()
... 
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 1, in <lambda>
NameError: global name 'Stick' is not defined
>>> 

We could write around this with a try/except -- but that's almost not worth talking about, because by simply creating a new instance with the same name as previous, lo and behold the event 'points' at the newly created instance of the object!

>>> Stick = A(777)
>>> for e in events:
...     e()()
... 
'my x is 778'
>>>

Note that a new object doesn't also have its foo and bar methods called - so this really is 'pointed' right at the same-named instance of the object.

>>> Dave = A(15) #sets Dave.x to 15
>>> for e in events:
...     e()()
... 
'my x is 779'
>>> Dave.x
15
>>> Dave.bar()
'my x is 15'
>>> 

This is the first time I've seen an object that can be so easily "re-called" after being deleted, or for that matter even successfully referencing a variable that doesn't. It 'feels' awfully magical.

The Python docs just liken lambdas to anonymous functions and don't really talk about the under-the-hood (that I can tell) -- so what exactly is going on when defining and using lambdas?

Side-question; is this fair game for an approach to pubsubbing, then, or is the convenience and apparent ubiquity of weakrefs the whole reason we tend to see those instead? (as opposed to, say, try/excepting your way through lambdas that no longer return valid functions or bound methods)

Was it helpful?

Solution

You are counting the wrong references. By creating a lambda, this lambda inherits the stack frame it is created from, containing all its local variables, in your case global variables. So you are creating more references to the stack frame, which contains only one reference to your actual object.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top