Strange classmethod mock behavior

Fabio Zadrozny fabiofz at gmail.com
Tue Oct 25 18:59:26 EDT 2011


On Tue, Oct 25, 2011 at 10:08 AM, Peter Otten <__peter__ at web.de> wrote:
> Fabio Zadrozny wrote:
>
>> I'm trying to mock a classmethod in a superclass but when restoring it
>> a strange behavior happens in subclasses (tested on Python 2.5)
>>
>> Anyone knows why this happens? (see test case below for details)
>> Is there any way to restore that original method to have the original
>> behavior?
>>
>> import unittest
>>
>> class Test(unittest.TestCase):
>>
>>     def testClassmethod(self):
>>         class Super():
>>             @classmethod
>>             def GetCls(cls):
>>                 return cls
>>
>>         class Sub(Super):
>>             pass
>>
>>         self.assertEqual(Sub.GetCls(), Sub)
>>
>>         original = Super.GetCls
>>         #Mock Super.GetCls, and do tests...
>>         Super.GetCls = original #Restore the original classmethod
>>         self.assertEqual(Sub.GetCls(), Sub) #The call to the
>> classmethod subclass returns the cls as Super and not Sub as expected!
>>
>> if __name__ == '__main__':
>>     unittest.main()
>
> [Not related to your problem] When working with descriptors it's a good idea
> to use newstyle classes, i. e. have Super inherit from object.
>
> The Super.GetCls attribute access is roughly equivalent to
>
> Super.__dict___["GetCls"].__get__(classmethod_instance, None, Super)
>
> and returns an object that knows about its class. So when you think you are
> restoring the original method you are actually setting the GetCls attribute
> to something else. You can avoid the problem by accessing the attribute
> explicitly:
>
>>>> class Super(object):
> ...     @classmethod
> ...     def m(cls): return cls
> ...
>>>> bad = Super.m
>>>> good = Super.__dict__["m"]
>>>> class Sub(Super): pass
> ...
>>>> Sub.m()
> <class '__main__.Sub'>
>>>> Super.m = bad
>>>> Sub.m()
> <class '__main__.Super'>
>>>> Super.m = good
>>>> Sub.m()
> <class '__main__.Sub'>
>

Hi Peter, thanks for the explanation.

Printing it helped me understand it even better...

print(Super.__dict__['GetCls'])
print(Super.GetCls)

<classmethod object at 0x022AF210>
<bound method type.GetCls of <class '__main__.Super'>>

Cheers,

Fabio



More information about the Python-list mailing list