【问题标题】:Python Overlays : A case for monkey PatchingPython Overlays:猴子补丁的一个案例
【发布时间】:2016-10-22 14:02:11
【问题描述】:

我正在尝试在 python 中包装/猴子补丁模块。我正在尝试开发一种不会干扰任何现有代码的干净方法来实现这一点。

问题

给定一个从 MODULE 导入一些 CLASS 的脚本

from MODULE import CLASS 

我想用另一个_MODULE_ 替换MODULE。其中_MODULE_ 是原始MODULE 的补丁。我能看到的最干净的界面如下。

from overlay import MODULE # Switches MODULE for _MODULE
from MODULE import CLASS   # Original import now uses _MODULE_

这基本上是猴子修补模块,就像猴子修补类、函数和方法一样。我相信,如果正确地做到这一点,人们可以以一种特定于项目的方式持续修补代码。

实现这一点的最佳方法是什么?

【问题讨论】:

  • 我不知道为什么有人投票结束我的问题,因为它过于宽泛,也许它含糊不清,但它肯定是一个重点问题。也许我有点冗长?无论哪种方式,我都只是编辑了整个内容并清理了一些内容。
  • 在帖子中很难找到问题。您必须仔细阅读大量的文本和编码示例。或许您应该提前说明问题,然后提供说明问题的所有代码。
  • 我删除了 MWE,如果有人问,我会将其作为答案发布,但现在已将其保留以保留我未答复的状态。希望这为更强大的方法打开了大门。

标签: python python-import monkeypatching


【解决方案1】:
>>> import wrapt
>>> @wrapt.when_imported('collections')
... def hook(collections):
...     OldOrderedDict = collections.OrderedDict
...     class MyOrderedDict(OldOrderedDict):
...         def monkey(self):
...             print('ook ook')
...     collections.OrderedDict = MyOrderedDict
...     
>>> from collections import OrderedDict
>>> OrderedDict().monkey()
ook ook

【讨论】:

    【解决方案2】:

    @wim 的答案当然更好,但我已经摆弄了一段时间,这是我最好的 bash。我假设以下文件夹/文件结构:

    PACKAGE/
      overlay.py
      _decimal_.py
      __main__.py
    

    _decimal_.py 中我包含以下几行

    from decimal import *
    __version__ = "X.Y"
    

    overlay.py我有:

    import importlib
    import inspect
    import builtins
    import os
    from pathlib import Path
    
    modsep      = '.'
    
    class OverlayImporter(object):
    
     def __init__(self, *args, path = None, root = None, _import_ = __import__, **kvps):
      super().__init__(*args, **kvps)
      self.mask = "_{}_"
      self.root = Path(root or os.path.dirname(inspect.getmodule(inspect.stack()[1][0]).__file__))
      self.mods = self.modules()
      # Substitutes Import Functionality
      builtins.__import__ = self
      self.imp = _import_
      self.lom = []
    
     def __call__(self, name, *args) : # (self, *args, *kvps):  
      # Hooks the import statement
      if self.mapToTarget(name) in self.mods.keys() :
       if name in self.lom :
        return self.imp(name, *args)
       self.lom.append(name)
       return importlib.import_module(self.mapToTarget(name)) # This is a little black magic as we ignore the args
      return self.imp(name, *args)
    
     def mapToTarget(self, name) :
      """Maps request to the overlay module"""
      # Converts PACKAGE.MODULE to overlay._PACKAGE_._MODULE_
      return modsep.join([self.mask.format(part) for part in name.split(modsep)])
    
     def modules(self) : 
      """ Lists the overlays implemented within a directory """
      ext = '.py'
      mod = lambda parts, ext : [part[:-len(ext)] if enum + 1 == len(parts) else part for enum, part in enumerate(parts)]
      lst = [(mod(file.relative_to(self.root).parts, ext), file) for file in self.root.rglob('*'+ext)]
      return {modsep.join(item[0][:-1]) if item[0][-1] == "__init__" else modsep.join(item[0]) : item[1] for item in lst}
    

    __main_.py我有

    from overlay import OverlayImporter
    OverlayImporter()
    import decimal
    print(decimal.__version__)
    

    注释主文件中的前两行会在已修补和未修补的十进制版本之间切换。

    【讨论】:

      猜你喜欢
      • 2012-12-18
      • 1970-01-01
      • 2011-04-15
      • 1970-01-01
      • 1970-01-01
      • 2017-10-05
      • 2016-09-01
      • 2012-09-16
      • 2020-01-09
      相关资源
      最近更新 更多