开发者

Python introspection: Automatic wrapping of methods

开发者 https://www.devze.com 2023-04-08 21:54 出处:网络
object of type A and Is there a way to programatically wrap a class object? Given class A(object): def __init__(self):

object of type A and Is there a way to programatically wrap a class object?

Given

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

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

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

I want another class that wraps an A, such as

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:
            ## ...

Is there a way to do create B.f0 & B.f1 by reflection/inspection o开发者_JS百科f class A?


If you want to create class B by calling a function on a predefined class A, you can simply do B = wrap_class(A) with a function wrap_class that looks like this:

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)

As Jürgen pointed out, this creates a copy of the class; this is only needed, however, if you really want to keep your original class A around (like suggested in the original question). If you don't care about A, you can simply decorate it with a wrapper that does not perform any copy, like so:

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

The decorated class A catches exceptions.

The metaclass version is heavier, but its principle is similar:

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)

This prints:

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

What you want to do is in fact typically done by a metaclass, which is a class whose instances are a class: this is a way of building the B class dynamically based on its parsed Python code (the code for class A, in the question). More information on this can be found in the nice, short description of metaclasses given in Chris's Wiki (in part 1 and parts 2-4).


Meta classes are an option, but generally hard to understand. As is too much reflection if not needed in simple cases, because it is easy to catch too many (internal) functions. If the wrapped functions are a stable known set, and B might gain other functions, you can delegate explicitly function by function and still keep your error handling code in one place:

class B(object):

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

You might do the assignments in a loop if they are many, using getattr/setattr.

The errorhandler function will need to return a function which wraps its argument with error handling code.

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

You can also use errorhandler as decorator on new functions not delegating to the A instance.

def B(A):
    ...
    @errorHandler
    def f_new(self):
        ...

This solution keeps B simple and it is quite explicit what's going on.


You could try it old-school with __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

Or with updating B's 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
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号