如何为Python库better_profanity的类方法打猴子补丁并解决变量未定义问题?
问题描述
我想要修改better_profanity库,为get_replacement_for_swear_word函数添加额外参数。操作步骤如下:
- 导入库并测试基础功能:
from better_profanity import profanity, Profanity text = "Nice c0ck" censored = profanity.censor(text) print(censored)
- 获取类方法源码,修改后通过
exec导入到当前作用域:
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' ) # 修复缩进 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)
其中我不得不将ALLOWED_CHARACTERS替换为self.ALLOWED_CHARACTERS,因为原库作者在类定义文件中导入了该变量,但替换方法后运行代码时会提示该变量未定义。好在这个变量同时存在于类实例的self中,但如果遇到其他仅在原模块中导入、未存储在self里的变量,该如何解决?
解决方案
方法1:在exec时注入依赖的模块变量
原方法依赖的模块级变量(比如ALLOWED_CHARACTERS)可以直接从原库的对应模块导入,然后在执行exec时将这些变量传入全局作用域,这样修改后的方法就能直接访问这些变量,无需替换为self.xxx。
示例代码:
from inspect import getsource from better_profanity import profanity, Profanity # 直接导入原方法依赖的模块变量 from better_profanity.constants import ALLOWED_CHARACTERS 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)' ) # 修复缩进,无需替换ALLOWED_CHARACTERS new_hide_swear_words = '\n'.join(i[4:] for i in new_hide_swear_words.split('\n')) # 将所需变量传入exec的全局作用域 exec(new_hide_swear_words, globals()) # 替换原实例的方法 profanity._hide_swear_words = _hide_swear_words.__get__(profanity, Profanity)
原理:exec的第二个参数指定了执行代码时的全局命名空间,导入的ALLOWED_CHARACTERS会被包含其中,修改后的方法就能像原方法一样直接引用该变量。
方法2:继承Profanity类重写目标方法
这是更稳妥、可维护性更高的方案,直接创建子类并重写_hide_swear_words方法,避免依赖原方法的源码字符串修改,同时可以自由访问原模块的变量或类的属性。
示例代码:
from better_profanity import Profanity, profanity from better_profanity.constants import ALLOWED_CHARACTERS class CustomProfanity(Profanity): def _hide_swear_words(self, text: str, censor_char: str) -> str: processed_text = [] i = 0 text_length = len(text) while i < text_length: current_char = text[i] # 跳过允许的非字母数字字符 if current_char in ALLOWED_CHARACTERS: processed_text.append(current_char) i += 1 continue # 查找连续的字母数字序列(可能包含脏话) j = i while j < text_length and text[j] not in ALLOWED_CHARACTERS: j += 1 cur_word = text[i:j].lower() # 检查是否是脏话,修改get_replacement_for_swear_word的调用参数 if cur_word in self._bad_words: replacement = self.get_replacement_for_swear_word(censor_char, cur_word) processed_text.append(replacement) else: processed_text.append(text[i:j]) i = j return ''.join(processed_text) # 使用自定义的实例替换默认profanity profanity = CustomProfanity() # 测试功能 text = "Nice c0ck" censored = profanity.censor(text) print(censored)
原理:子类可以直接导入原库的模块变量,也可以通过self访问父类的属性,完全复刻原方法逻辑后,按需修改get_replacement_for_swear_word的调用即可,不会出现变量未定义的问题,且不受原库版本更新时源码格式变化的影响。
内容的提问来源于stack exchange,提问作者user9102437
相关产品推荐
相关产品推荐

