代码之家  ›  专栏  ›  技术社区  ›  dm03514

在python3中是否可以完全修复内置的str

  •  15
  • dm03514  · 技术社区  · 6 年前

    我正在尝试修补python的内置 str 为了追踪所有 STR 分配。我遇到了一些问题,想知道是否有人可以看到我做错了什么,或者这是否可能通过在蟒蛇3猴子修补本机?(以下代码在Python2.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
    

    以及在sre_编译 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类的理解之外:p)


    从REPL来看,上面的示例可以工作,但是在nose和unittests的上下文中不起作用…

    ⟫ 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
    
    1 回复  |  直到 6 年前
        1
  •  3
  •   zwer    6 年前

    如果你坚持用猴子修补内置的 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
    

    您可能还想检查 t 属于 my_isinstance() 是元组并对其进行迭代以确保替换 mystr 具有 old_str 也。