Python introspection: автоматическая упаковка методов - PullRequest
2 голосов
/ 29 сентября 2011

объект типа A и есть ли способ программно обернуть объект класса?

Учитывая

class A(object):
    def __init__(self):
        ## ..

    def f0(self, a):
        ## ...

    def f1(self, a, b):
        ## ..

Я хочу другой класс, который обертывает A, например

class B(object):
    def __init__(self):
        self.a = A()

    def f0(self,a):
        try:
            a.f0(a)
        except (Exception),ex:
            ## ...

    def f1(self, a, b):
        try:
            a.f1(a,b)
        except (Exception),ex:
            ## ...

Есть ли способ создать B.f0 & B.f1 путем отражения / проверки класса A?

Ответы [ 3 ]

4 голосов
/ 29 сентября 2011

Если вы хотите создать класс B, вызвав функцию из предопределенного класса A, вы можете просто сделать B = wrap_class(A) с функцией wrap_class, которая выглядит следующим образом:

import copy

def wrap_class(cls):
    'Wraps a class so that exceptions in its methods are caught.'
    # The copy is necessary so that mutable class attributes are not
    # shared between the old class cls and the new class:
    new_cls = copy.deepcopy(cls)
    # vars() is used instead of dir() so that the attributes of base classes
    # are not modified, but one might want to use dir() instead:
    for (attr_name, value) in vars(cls).items():
        if isinstance(value, types.FunctionType):
            setattr(new_cls, attr_name, func_wrapper(value))
    return new_cls

B = wrap_class(A)

Как отметил Юрген, это создает копию класса; однако это необходимо только в том случае, если вы действительно хотите сохранить свой оригинальный класс A (как предложено в исходном вопросе). Если вас не волнует A, вы можете просто украсить его оболочкой, которая не выполняет копирование, например:

def wrap_class(cls):
    'Wraps a class so that exceptions in its methods are caught.'
    # vars() is used instead of dir() so that the attributes of base classes
    # are not modified, but one might want to use dir() instead:
    for (attr_name, value) in vars(cls).items():
        if isinstance(value, types.FunctionType):
            setattr(cls, attr_name, func_wrapper(value))
    return cls

@wrap_class
class A(object):
    …  # Original A class, with methods that are not wrapped with exception catching

Украшенный класс A ловит исключения.

Версия метакласса тяжелее, но ее принцип аналогичен:

import types

def func_wrapper(f):

    'Returns a version of function f that prints an error message if an exception is raised.'

    def wrapped_f(*args, **kwargs):
        try:
            return f(*args, **kwargs)
        except Exception, ex:
            print "Function", f, "raised", ex

    return wrapped_f

class ExceptionCatcher(type):

    'Metaclass that wraps methods with func_wrapper().'

    def __new__(meta, cname, bases, cdict):
        # cdict contains the attributes of class cname:
        for (attr_name, value) in cdict.items():
            if isinstance(value, types.FunctionType):  # Various attribute types can be wrapped differently
                cdict[attr_name] = func_wrapper(value)
        return super(meta, ExceptionCatcher).__new__(meta, cname, bases, cdict)

class B(object):

    __metaclass__ = ExceptionCatcher  # ExceptionCatcher will be used for creating class A

    class_attr = 42  # Will not be wrapped

    def __init__(self):
        pass

    def f0(self, a):
        return a*10

    def f1(self, a, b):
        1/0  # Raises a division by zero exception!

# Test:
b = B()
print b.f0(3.14)
print b.class_attr
print b.f1(2, 3)

Это печатает:

31.4
42
Function <function f1 at 0x107812d70> raised integer division or modulo by zero
None

То, что вы хотите сделать, на самом деле обычно делается метаклассом, который является классом, экземплярами которого является класс: это способ динамического создания класса B на основе его разобранного кода Python (кода для класса A, в вопросе). Дополнительную информацию об этом можно найти в хорошем, коротком описании метаклассов, приведенном в вики Криса (в часть 1 и части 2-4 ).

2 голосов
/ 29 сентября 2011

Метаклассы являются опцией, но, как правило, трудно понять.Это слишком много размышлений, если не нужно в простых случаях, потому что легко поймать слишком много (внутренних) функций.Если обернутые функции представляют собой стабильный известный набор, и B может получать другие функции, вы можете явно делегировать функцию по функции и при этом хранить код обработки ошибок в одном месте:

class B(object):

    def __init__(self):
        a = A()
        self.f0 = errorHandler(a.f0)  
        self.f1 = errorHandler(a.f1)

Вы можете выполнять назначения вцикл, если их много, используя getattr / setattr.

Функция обработчика ошибок должна будет возвращать функцию, которая оборачивает свой аргумент кодом обработки ошибки.

def errorHandler(f):
    def wrapped(*args, **kw):
        try:
            return f(*args, **kw)
        except:
            # log or something
    return wrapped

Вы также можете использовать обработчик ошибокв качестве декоратора для новых функций, не делегирующих экземпляру A

0 голосов
/ 29 сентября 2011

Вы можете попробовать его в старой школе с __getattr__:

class B(object):
  def __init__(self):
    self.a = A()
  def __getattr__(self, name):
    a_method = getattr(a, name, None)
    if not callable(a_method):
      raise AttributeError("Unknown attribute %r" % name)
    def wrapper(*args, **kwargs):
      try:
        return a_method(*args, **kwargs)
      except Exception, ex:
        # ...
    return wrapper

Или с обновлением B dict:

class B(object):
  def __init__(self):
    a = A()
    for attr_name in dir(a):
      attr = getattr(a, attr_name)
      if callable(attr):
        def wrapper(*args, **kwargs):
          try:
            return attr(*args, **kwargs)
          except Exception, ex:
            # ...
        setattr(self, attr_name, wrapper) # or try self.__dict__[x] = y
...