是否可以在 python3 中完全使用 Monkey Patch 内置的`str`

Posted

技术标签:

【中文标题】是否可以在 python3 中完全使用 Monkey Patch 内置的`str`【英文标题】:Is it possible to fully Monkey Patch builtin `str` in python3 【发布时间】:2018-11-12 23:52:41 【问题描述】:

我正在尝试修补 python 的内置 str 以跟踪所有 str 分配的计数。我遇到了一些问题,想知道是否有人能看到我做错了什么,或者这是否甚至可以通过 python3 中的猴子补丁本地实现? (以下在 python 2.7.12 中运行良好)

$ python
Python 3.5.2 (default, Nov 23 2017, 16:37:01)
[GCC 5.4.0 20160609] on linux

我首先天真地尝试修补str,好像它是一个函数:

def patch_str_allocations():
  old_str = str
  def mystr(*args, **kwargs):
    return old_str(*args, **kwargs)

  builtins.str = mystr


def test():
  logger = logging.getLogger(__name__)
  patch_str_allocations()
  logger.debug(str('test'))

当然,这会导致字符串用于 isinstance 等各种操作失败

    logger.debug(route)
  File "/usr/lib/python3.5/logging/__init__.py", line 1267, in debug
    self._log(DEBUG, msg, args, **kwargs)
  File "/usr/lib/python3.5/logging/__init__.py", line 1403, in _log
    fn, lno, func, sinfo = self.findCaller(stack_info)
  File "/usr/lib/python3.5/logging/__init__.py", line 1360, in findCaller
    filename = os.path.normcase(co.co_filename)
  File "/home/ubuntu/.virtualenvs/papm/lib/python3.5/posixpath.py", line 52, in normcase
    if not isinstance(s, (bytes, str)):
TypeError: isinstance() arg 2 must be a type or tuple of types

然后我尝试了一种基于类的方法:

class StrAllocator(str):
    oldstr = None

    def __new__(cls, *args, **kwargs):
        return StrAllocator.oldstr.__new__(cls, *args, **kwargs)

    @property
    def __class__(self):
        return str


def patch_str_allocations():
    StrAllocator.oldstr = str
    builtins.str = StrAllocator

在正常的 str 构造中,这工作正常,但仍然遇到一些问题:

class StrAllocatorTestCase(unittest.TestCase):

    def test_log(self):
        t1 = str('t1')
        logger = logging.getLogger(__name__)
        patch_str_allocations()
        t2 = str('t2')
        print(type(t1))
        print(type(t2))
        print(isinstance(t1, str))
        print(isinstance(t2, StrAllocator))
        print(isinstance(t2, str))
        logger.debug(str('test'))


$ nosetests tests.test_str_allocator:StrAllocatorTestCase.test_log -s

<class 'str'>
<class 'pythonapm.instruments.allocations.StrAllocator'>
False
True
True
E
======================================================================
ERROR: test_log (tests.instruments.test_str_allocator.StrAllocatorTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/vagrant_data/github.com/dm03514/python-apm/tests/instruments/test_str_allocator.py", line 30, in test_log
    logger.debug(str('test'))
  File "/usr/lib/python3.5/logging/__init__.py", line 1267, in debug
    self._log(DEBUG, msg, args, **kwargs)
  File "/usr/lib/python3.5/logging/__init__.py", line 1403, in _log
    fn, lno, func, sinfo = self.findCaller(stack_info)
  File "/usr/lib/python3.5/logging/__init__.py", line 1360, in findCaller
    filename = os.path.normcase(co.co_filename)
  File "/home/ubuntu/.virtualenvs/papm/lib/python3.5/posixpath.py", line 54, in normcase
    "not ''".format(s.__class__.__name__))
TypeError: normcase() argument must be str or bytes, not 'str'

----------------------------------------------------------------------
Ran 1 test in 0.003s

以及在isstring 上的 sre_compile 中检查

  File "/home/ubuntu/.virtualenvs/papm/lib/python3.5/site-packages/flask/app.py", line 1250, in decorator                                                         [0/9965]
    self.add_url_rule(rule, endpoint, f, **options)
  File "/home/ubuntu/.virtualenvs/papm/lib/python3.5/site-packages/flask/app.py", line 66, in wrapper_func
    return f(self, *args, **kwargs)
  File "/home/ubuntu/.virtualenvs/papm/lib/python3.5/site-packages/flask/app.py", line 1216, in add_url_rule
    self.url_map.add(rule)
  File "/home/ubuntu/.virtualenvs/papm/lib/python3.5/site-packages/werkzeug/routing.py", line 1215, in add
    rule.bind(self)
  File "/home/ubuntu/.virtualenvs/papm/lib/python3.5/site-packages/werkzeug/routing.py", line 687, in bind
    self.compile()
  File "/home/ubuntu/.virtualenvs/papm/lib/python3.5/site-packages/werkzeug/routing.py", line 751, in compile
    self._regex = re.compile(regex, re.UNICODE)
  File "/home/ubuntu/.virtualenvs/papm/lib/python3.5/re.py", line 224, in compile
    return _compile(pattern, flags)
  File "/home/ubuntu/.virtualenvs/papm/lib/python3.5/re.py", line 292, in _compile
    raise TypeError("first argument must be string or compiled pattern")
TypeError: first argument must be string or compiled pattern

任何人都可以看到缺少什么吗? (除了我对描述符和python类的理解:p)


从 REPL 中,上面的示例有效,但在鼻子和单元测试的上下文中不起作用...

⟫ ipython
Python 3.5.2 (default, Nov 23 2017, 16:37:01)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.4.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import logging

In [2]: import builtins

In [3]: class StrAllocator(str):
   ...:     oldstr = None
   ...:
   ...:     def __new__(cls, *args, **kwargs):
   ...:         return StrAllocator.oldstr.__new__(cls, *args, **kwargs)
   ...:
   ...:     @property
   ...:     def __class__(self):
   ...:         return str
   ...:
   ...:
In [4]: def patch_str_allocations():                                                                                                                                    [6/9733]
   ...:     StrAllocator.oldstr = str
   ...:     builtins.str = StrAllocator
   ...:

In [5]:   def test_log():
   ...:         t1 = str('t1')
   ...:         logger = logging.getLogger(__name__)
   ...:         patch_str_allocations()
   ...:         t2 = str('t2')
   ...:         print(type(t1))
   ...:         print(type(t2))
   ...:         print(isinstance(t1, str))
   ...:         print(isinstance(t2, StrAllocator))
   ...:         print(isinstance(t2, str))
   ...:         logger.debug(str('test'))
   ...:
In [6]: test_log()
<class 'str'>
<class '__main__.StrAllocator'>
False
True
True

【问题讨论】:

我无法重现那个 TypeError。你能扔掉那些Surfacers的东西并提供一个真正的MCVE吗? 顺便说一句,覆盖str 不会影响字符串文字和大多数内置函数。 (例如,str.join 将创建一个新字符串,而无需调用您覆盖的 str。) @dm03514,您列出了问题但从未提及您的用例?可能有不同/更好的方法来解决问题 @TarunLalwani 我正在尝试通过内置的 str 方法跟踪所有 str 分配的计数,我希望在调用 str() 时增加一个计数器 您缺少什么:在您修补之前创建的所有字符串 现在都无法通过 isinstance() 测试.. 【参考方案1】:

如果您坚持使用自己的函数对内置 str 进行猴子修补,为什么不同时对 isinstance() 进行猴子修补,以确保它将您的函数视为内置 str ?比如:

def patch_str_allocations():
    old_str = str
    old_isinstance = builtins.isinstance

    def mystr(*args, **kwargs):
        return old_str(*args, **kwargs)

    def my_isinstance(o, t):
        if t is mystr:
            t = old_str
        return old_isinstance(o, t)

    builtins.str = mystr
    builtins.isinstance = my_isinstance

您可能还想检查my_isinstance() 中的t 是否是一个元组并对其进行迭代以确保您也将mystr 替换为old_str

【讨论】:

以上是关于是否可以在 python3 中完全使用 Monkey Patch 内置的`str`的主要内容,如果未能解决你的问题,请参考以下文章

App测试:Monke压力测试

App测试:Monke压力测试

App测试:Monke压力测试

pymystem3使用

Python3,Bio Entrez,PubMed:是否可以获取一篇文章被引用的次数?

python3不完全支持python2的缩进策略对吗