Andrea Chioara's answer is basically correct:
Randomness arises from Python 3.3 and later randomized default hash orders (see Why is a dictionary not deterministic?.
/ li>Accessing x calls the lambda function, which was bound to __getattribute__ .
See The difference between __getattr__ vs __getattribute__ and the Python3 directory reference notes for object.__getattribute__ .
We can make all this a lot less confusing:
class t(object): def __getattribute__(self, name): use = None for val in vars(object).values(): if callable(val) and type(val) is not type: use = val return use def super_serious(obj): proxy = t() return proxy
which is what happens to lambda. Please note that in the loop we do not bind / save the current value of val . 1 This means that we get the last value that val has in the function. With the source code, we do all this work at the time of creating the object t , and not later, when we call t.__getattribute__ , but it still comes down to: Of <name, value> pairs in vars (object), find the last one that meets our criteria : the value must be callable, while the type of the value is not type itself.
Using class t(object) makes the class object t a new style even in Python2, so this code now "works" in Python2 as well as Python3. Of course, in Py2k, word ordering is not randomized, so we always get the same thing every time:
$ python2 foo3.py <slot wrapper '__init__' of 'object' objects> $ python2 foo3.py <slot wrapper '__init__' of 'object' objects>
vs
$ python3 foo3.py <slot wrapper '__eq__' of 'object' objects> $ python3 foo3.py <slot wrapper '__lt__' of 'object' objects>
Setting the PYTHONHASHSEED environment PYTHONHASHSEED to 0 makes order deterministic in Python3:
$ PYTHONHASHSEED=0 python3 foo3.py <method '__subclasshook__' of 'object' objects> $ PYTHONHASHSEED=0 python3 foo3.py <method '__subclasshook__' of 'object' objects> $ PYTHONHASHSEED=0 python3 foo3.py <method '__subclasshook__' of 'object' objects>
1 To find out what this means, try the following:
def f(): i = 0 ret = lambda: i for i in range(3): pass return ret func = f() print('func() returns', func())
Note that it says func() returns 2 , not func() return 0 . Then replace the lambda line with:
ret = lambda stashed=i: stashed
and run it again. Now the function returns 0. This is because we saved the current value of i here.
If we did the same thing in the sample program, it would return the first val , which would meet the criteria, not the last.