Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python unable to compare bound method to itself

Tags:

python

I am attempting to write a test that checks if a variable holding the bound method of a class is the same as another reference to that method. Normally this is not a problem, but it does not appear to work when done within another method of the same class. Here is a minimal example:

class TestClass:
    def sample_method(self):
        pass
    def test_method(self, method_reference):
        print(method_reference is self.sample_method)

I am really using an assert instead of print, but that is neither here nor there since the end result is the same. The test is run as follows:

instance = TestClass()
instance.test_method(instance.sample_method)

The result is False even though I am expecting it to be True. The issue manifests itself in both Python 3.5 and Python 2.7 (running under Anaconda).

I understand that bound methods are closures that are acquired by doing something like TestClass.test_method.__get__(instance, type(instance)). However, I would expect that self.sample_method is already a reference to such a closure, so that self.sample_method and instance.sample_method represent the same reference.

Part of what is confusing me here is the output of the real pytest test that I am running (working on a PR for matplotlib):

assert <bound method TestTransformFormatter.transform1 of <matplotlib.tests.test_ticker.TestTransformFormatter object at 0x7f0101077b70>> is <bound method TestTransformFormatter.transform1 of <matplotlib.tests.test_ticker.TestTransformFormatter object at 0x7f0101077b70>>
E        +  where <bound method TestTransformFormatter.transform1 of <matplotlib.tests.test_ticker.TestTransformFormatter object at 0x7f0101077b70>> = <matplotlib.ticker.TransformFormatter object at 0x7f0101077e10>.transform
E        +  and   <bound method TestTransformFormatter.transform1 of <matplotlib.tests.test_ticker.TestTransformFormatter object at 0x7f0101077b70>> = <matplotlib.tests.test_ticker.TestTransformFormatter object at 0x7f0101077b70>.transform1

If I understand the output correctly, the actual comparison (the first line) is really comparing the same objects, but somehow turning up False. The only thing I can imagine at this point is that __get__ is in fact being called twice, but I know neither why/where/how, nor how to work around it.

like image 522
Mad Physicist Avatar asked Jan 27 '17 18:01

Mad Physicist


2 Answers

They're not the same reference - the objects representing the two methods occupy different locations in memory:

>>> class TestClass:
...     def sample_method(self):
...         pass
...     def test_method(self, method_reference):
...         print(hex(id(method_reference)))
...         print(hex(id(self.sample_method)))
... 
>>> instance = TestClass()
>>> instance.test_method(instance.sample_method)
0x7fed0cc561c8
0x7fed0cc4e688

Changing to method_reference == self.sample_method will make the assert pass, though.

Edit since question was expanded: seems like a flawed test - probably the actual functionality of the code does not require the references to be the same (is), just equal (==). So your change probably didn't break anything except for the test.

like image 89
1'' Avatar answered Sep 18 '22 09:09

1''


While the accepted answer is in no way incorrect, it seems like it should be noted that methods are bound on attribute lookup. Furthermore, the behavior of unbound methods changes between Python 2.X and Python 3.X.

class A:
    def method(self):
        pass

a = A()
print(a.method is a.method) # False

print(A.method is A.method) # Python 3.X: True, Python 2.X: False
like image 41
Jared Goguen Avatar answered Sep 19 '22 09:09

Jared Goguen