4

In python you cannot directly compare functions created by lambda expressions:

>>> (lambda x: x+2) == (lambda x: x+2)
False

I made a routine to hash the disassembly.

import sys
import dis
import hashlib
import contextlib


def get_lambda_hash(l, hasher=lambda x: hashlib.sha256(x).hexdigest()):
    @contextlib.contextmanager
    def capture():
        from cStringIO import StringIO
        oldout, olderr = sys.stdout, sys.stderr
        try:
            out=[StringIO(), StringIO()]
            sys.stdout, sys.stderr = out
            yield out
        finally:
            sys.stdout, sys.stderr = oldout, olderr
            out[0] = out[0].getvalue()
            out[1] = out[1].getvalue()

    with capture() as out:
        dis.dis(l)

    return hasher(out[0])

The usage is:

>>>> get_lambda_hash(lambda x: x+2) == get_lambda_hash(lambda x: x+1)
False

>>>> get_lambda_hash(lambda x: x+2) == get_lambda_hash(lambda x: x+2)
True

Is there any more elegant solution for this problem?

Terry Jan Reedy
  • 18,414
  • 3
  • 40
  • 52
sashab
  • 1,534
  • 2
  • 19
  • 36
  • 8
    If you need comparisons, perhaps a `lambda` isn't the tool for the job? – jonrsharpe Feb 20 '15 at 17:03
  • Should the expressions be *syntactical* or *semantical* equal? The latter can't be done at all. – Willem Van Onsem Feb 20 '15 at 17:10
  • @jonrsharpe consider you have a library to read long files with rows and columns into memory. You can specify columns you want to select, and also you can specify lambdas to do some trickery when selecting e.g. select sum of two columns. Now you want to do cache where key is which columns(and lambdas!) you select. – sashab Feb 20 '15 at 17:10
  • Yes, but it's not clear why that has led you to this requirement, which seems to indicate potential for broader improvements in design. This is likely an XY problem; see e.g. http://meta.stackexchange.com/q/66377/248731 – jonrsharpe Feb 20 '15 at 17:11
  • @CommuSoft syntactical – sashab Feb 20 '15 at 17:11
  • For example, rather than allow arbitrary lambdas to be passed, define generic functions with arguments, then cache by the key of the function in some dictionary of functions and the arguments to pass to it. – jonrsharpe Feb 20 '15 at 17:12
  • @jonrsharpe no, it's not XY problem. The design is fine. It is just speed-improvement in some cases. – sashab Feb 20 '15 at 17:13
  • So you seem to think; my point is that a requirement to compare arbitrary lambdas *may suggest that it isn't*. – jonrsharpe Feb 20 '15 at 17:14
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/71346/discussion-between-scrat-and-jonrsharpe). – sashab Feb 20 '15 at 17:15
  • Duplicate of [Check if two Python functions are equal - Stack Overflow](https://stackoverflow.com/questions/20059011/check-if-two-python-functions-are-equal) – user202729 Jun 18 '22 at 10:03

1 Answers1

6

If you insist on performing this insane bit of insanity, compare the bytecode and constants of each.

>>> import operator
>>> coco = operator.attrgetter('co_code', 'co_consts')
>>> coco((lambda x: x+2).__code__) == coco((lambda x: x+2).__code__)
True
>>> coco((lambda x: x+2).__code__) == coco((lambda x: x+1).__code__)
False
>>> def foo(y):
...   return y + 2
... 
>>> coco((lambda x: x+2).__code__) == coco(foo.__code__)
True
Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
  • 2
    This might be not good enough - for example for `def f(x): return lambda y: x+y` it would consider `f(1)` and `f(2)` to be the same function. – zch Feb 21 '15 at 16:56
  • Also not good if data used in lambda changed, like if in `lambda x: some_dict[x]` dictionary `some_dict` changed. – sashab Apr 24 '15 at 17:52