是否可以在 python3 中完全内置 `str` 的 Monkey Patch
Is it possible to fully Monkey Patch builtin `str` in python3
我正在尝试修补 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 的方法:
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
以及 sre_compile 上 isstring
检查
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 classes :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
如果您坚持用自己的函数对内置 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
。
我正在尝试修补 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 的方法:
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
以及 sre_compile 上 isstring
检查
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 classes :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
如果您坚持用自己的函数对内置 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
。