Python覆盖:猴子补丁的案例



我试图在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_

这基本上是在给模块打补丁,就像给类、函数和方法打补丁一样。我相信如果这是正确的,一个人可以始终如一地以特定项目的方式修补代码。

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

>>> 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

@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__)

注释主文件中的前两行会在打过补丁的和未打过补丁的decimal版本之间切换。

最新更新