Home > Software design >  How to monkeypatch a python library class method?
How to monkeypatch a python library class method?

Time:01-25

I am trying to modify a better_profanity library to include an additional argument to get_replacement_for_swear_word function. To do so I first import the necessary parts of the library and test its functionality before:

from better_profanity import profanity, Profanity

text = "Nice c0ck"
censored = profanity.censor(text)
print(censored)

Now I get the source code of the class method, modify it and execute it to __main___:

from inspect import getsource
new_hide_swear_words = getsource(profanity._hide_swear_words).replace(
    'get_replacement_for_swear_word(censor_char)', 'get_replacement_for_swear_word(censor_char, cur_word)').replace(
        'ALLOWED_CHARACTERS', 'self.ALLOWED_CHARACTERS'
    )
# fixing the indent
new_hide_swear_words = '\n'.join(i[4:] for i in new_hide_swear_words.split('\n')) 
exec(new_hide_swear_words)

Now I replace this function inside the class:

profanity._hide_swear_words = _hide_swear_words.__get__(profanity, Profanity)

Note that I swap ALLOWED_CHARACTERS for self.ALLOWED_CHARACTERS. This is because the author of the library has imported ALLOWED_CHARACTERS in the same file where the class is defined, so when I swap the function and try to run the first piece of code again, it sais that this variable is not defined. It just so happens that it is stored in self as well, but there is no such luck with several other imported modules. Any ideas how to tackle this?

Here is the class definition on github.

CodePudding user response:

When you run exec(new_hide_swear_words), you define the function _hide_swear_words in your current module (that's why you can access it later with just _hide_swear_words).

That however means, that the function lives fully in your module, so when you call it indirectly with profanity.censor(some_text) it will run the function inside this module and look for all dependent global symbols in your module. That's why it can't access the variable ALLOWED_CHARACTERS or the function any_next_words_form_swear_words. They are defined in the profanity module, but not in your module where you run the exec.

One way to solve this, would be to just import all symbols into your module.

from inspect import getsource

from better_profanity import Profanity, profanity
from better_profanity.constants import ALLOWED_CHARACTERS
from better_profanity.utils import *

new_hide_swear_words = getsource(profanity._hide_swear_words)
new_hide_swear_words = "\n".join(i[4:] for i in new_hide_swear_words.split("\n"))
exec(new_hide_swear_words)

profanity._hide_swear_words = _hide_swear_words.__get__(profanity, Profanity)

text = "Nice c0ck"
censored = profanity.censor(text)
print(censored)

Another way would be to execute the function in the profanity module itself (then all the symbols are already defined). However that also has a little overhead. E.g. you have to import the module and pass it to the exec function, and afterwards you need to extract the function from the module (as it will be defined in that module).

from importlib import import_module
from inspect import getsource
from better_profanity import Profanity, profanity

new_hide_swear_words = getsource(profanity._hide_swear_words)
# fixing the indent
new_hide_swear_words = "\n".join(i[4:] for i in new_hide_swear_words.split("\n"))
profanity_module = import_module(Profanity.__module__)
exec(new_hide_swear_words, vars(profanity_module))

profanity._hide_swear_words = profanity_module._hide_swear_words.__get__(
    profanity, Profanity
)

text = "Nice c0ck"
censored = profanity.censor(text)
print(censored)

profanity_module = import_module(Profanity.__module__) is the same thing as import better_profanity.better_profanity as profanity_module.

  • Related