Search code examples
pythonmethodsmonkeypatchingslots

Monkeypatch method of instance from class with __slots __


I've been trying to work around a bug in a third party module. The bug was fixed recently, but I've been wondering about my issues nevertheless. Perhaps this question may help me or others in the future.

The module defines a class Runtime with several methods that I use. Upon importing the module it automatically creates an instance of Runtime, loads configuration and gives the instance to the user (me) to work with.

# thirdPartyModule.py

class Runtime:
    def __init__(self, a):
        self.a = a

    def configuration(self, ...):
        ...

    def fun(self):
        print(self.a)

rt = Runtime("twice")
rt.configuration(...)

Unfortunately, one of the runtime methods contained a bug. I used to monkeypatch this bug by just overwriting the method of the instance by a working substitute like so:

# mycode.py

import types
from thirdPartyModule import rt

def newfun(self):
    print(self.a, self.a)

rt.fun = types.MethodType(newfun, rt)
rt.fun()

This worked just fine while I was waiting for the developer to fix the bug. Or at least it did up to the point where the developer added __slots__ to the class:

# thirdPartyModule.py

class Runtime:
    __slots__ = ("a", )

    def __init__(self, a):
        self.a = a

    ...

Since then I've tried several method-overwriting solutions in this forum, but all of them were rejected ("AttributeError: 'Runtime' object attribute 'fun' is read-only").

It might be that unittest.mock can help me out, but I'm not too familiar with mocking. I've tried the following without success:

import unittest.mock

rt.fun = mock.Mock(rt.fun, side_effect=newfun)

This results in the same AttributeError. Perhaps I could mock the entire instance (rt = mock.Mock(rt)) or something similar, but I'm neither familiar nor completely comfortable with this approach.

As I said the bug has been fixed in the meantime, but I cannot help but wonder how you would proceed in this situation?


Solution

  • >>> class X():
    ...     __slots__ = ("a",)
    ...     def func():
    ...         print("1")
    ... 
    >>> x = X()
    >>> type(x)
    <class '__main__.X'>
    >>> type(x).func = lambda self: print("2")
    >>> x.func
    <bound method <lambda> of <__main__.X object at 0x7f27fb88b050>>
    >>> x.func()
    2
    >>> 
    

    Explanation: you have to monkeypatch the Runtime class instead of the instance.