Обязательно ли нужно модифицировать сам класс? Или цель просто заменить то, что object.method () делает в определенный момент во время выполнения?
Я спрашиваю, потому что я обошел проблему фактической модификации класса, чтобы он вызывал специфичные для патчей вызовы методов в моей структуре с помощью getattribute и Runtime Decorator на моем объекте наследования Base.
Методы, извлеченные базовым объектом в getattribute , помещаются в Runtime_Decorator, который анализирует методы, вызывающие аргументы ключевых слов для применения патчей декораторов / обезьян.
Это позволяет вам использовать синтаксис object.method (monkey_patch = "mypatch"), object.method (decorator = "mydecorator") и даже object.method (decorators = my_decorator_list).
Это работает для любого отдельного вызова метода (я опускаю магические методы), делает это без фактического изменения каких-либо атрибутов класса / экземпляра, может использовать произвольные, даже сторонние методы для исправления, и будет прозрачно работать на подклассах, которые наследуются от Base ( при условии, что они не переопределяют getattribute конечно).
import trace
def monkey_patched(self, *args, **kwargs):
print self, "Tried to call a method, but it was monkey patched instead"
return "and now for something completely different"
class Base(object):
def __init__(self):
super(Base, self).__init__()
def testmethod(self):
print "%s test method" % self
def __getattribute__(self, attribute):
value = super(Base, self).__getattribute__(attribute)
if "__" not in attribute and callable(value):
value = Runtime_Decorator(value)
return value
class Runtime_Decorator(object):
def __init__(self, function):
self.function = function
def __call__(self, *args, **kwargs):
if kwargs.has_key("monkey_patch"):
module_name, patch_name = self._resolve_string(kwargs.pop("monkey_patch"))
module = self._get_module(module_name)
monkey_patch = getattr(module, patch_name)
return monkey_patch(self.function.im_self, *args, **kwargs)
if kwargs.has_key('decorator'):
decorator_type = str(kwargs['decorator'])
module_name, decorator_name = self._resolve_string(decorator_type)
decorator = self._get_decorator(decorator_name, module_name)
wrapped_function = decorator(self.function)
del kwargs['decorator']
return wrapped_function(*args, **kwargs)
elif kwargs.has_key('decorators'):
decorators = []
for item in kwargs['decorators']:
module_name, decorator_name = self._resolve_string(item)
decorator = self._get_decorator(decorator_name, module_name)
decorators.append(decorator)
wrapped_function = self.function
for item in reversed(decorators):
wrapped_function = item(wrapped_function)
del kwargs['decorators']
return wrapped_function(*args, **kwargs)
else:
return self.function(*args, **kwargs)
def _resolve_string(self, string):
try: # attempt to split the string into a module and attribute
module_name, decorator_name = string.split(".")
except ValueError: # there was no ".", it's just a single attribute
module_name = "__main__"
decorator_name = string
finally:
return module_name, decorator_name
def _get_module(self, module_name):
try: # attempt to load the module if it exists already
module = modules[module_name]
except KeyError: # import it if it doesn't
module = __import__(module_name)
finally:
return module
def _get_decorator(self, decorator_name, module_name):
module = self._get_module(module_name)
try: # attempt to procure the decorator class
decorator_wrap = getattr(module, decorator_name)
except AttributeError: # decorator not found in module
print("failed to locate decorators %s for function %s." %\
(kwargs["decorator"], self.function))
else:
return decorator_wrap # instantiate the class with self.function
class Tracer(object):
def __init__(self, function):
self.function = function
def __call__(self, *args, **kwargs):
tracer = trace.Trace(trace=1)
tracer.runfunc(self.function, *args, **kwargs)
b = Base()
b.testmethod(monkey_patch="monkey_patched")
b.testmethod(decorator="Tracer")
#b.testmethod(monkey_patch="external_module.my_patch")
Недостатком этого подхода является getattribute hooks all доступ к атрибутам, поэтому проверка и потенциальная упаковка методов происходит даже для атрибутов, которые не являются методами +, не будет используя функцию для конкретного звонка. А использование getattribute вообще немного сложное.
Фактическое влияние этих накладных расходов на мой опыт / для моих целей было незначительным, и моя машина работает на двухъядерном Celeron. В предыдущей реализации я использовал интроспективные методы для объекта init и затем связывал Runtime_Decorator с методами. Выполнение этих действий избавило от необходимости использовать getattribute и уменьшило накладные расходы, упомянутые ранее ... однако, это также нарушает рассол (возможно, не укроп) и менее динамично, чем этот подход.
Единственные случаи использования, с которыми я действительно столкнулся "в дикой природе" с этой техникой, были с декораторами синхронизации и трассировки. Однако возможности, которые он открывает, чрезвычайно широки.
Если у вас есть ранее существующий класс, который нельзя сделать наследующим от другой базы (или использовать технику, которая определена его собственным классом или в его базовом классе '), то, к сожалению, все это вообще не относится к вашей проблеме. .
Не думаю, что установка / удаление не вызываемых атрибутов в классе во время выполнения обязательно так сложно? если только вы не хотите, чтобы классы, которые наследуются от измененного класса, также автоматически отражали изменения сами по себе ... Хотя это звучит целым червем "просто невозможно" по звуку этого.