Python 反射:方法的自动封装

2 投票
3 回答
561 浏览
提问于 2025-04-17 03:18

有一个类型为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:
            ## ...

有没有办法通过反射或者检查类A来创建 B.f0B.f1 呢?

3 个回答

0

你可以试试老办法,用 __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 的字典:

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
2

元类是一种选择,但通常比较难理解。如果在简单的情况下不需要太多反射,反而容易抓取到太多内部函数。如果你包裹的函数是一组稳定且已知的,而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实例的新函数上。

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

这个解决方案让B保持简单,而且很清楚发生了什么。

4

如果你想通过调用一个函数来创建类 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)

正如 Jürgen 指出的,这样做会创建一个类的副本;不过,这种做法只有在你真的想保留原来的类 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

你想做的事情通常是通过元类来完成的,元类是一种类,它的实例也是类:这是一种根据解析后的 Python 代码动态构建 B 类的方法(在问题中就是类 A 的代码)。关于这方面的更多信息,可以在 Chris 的 Wiki 上找到一个简短而清晰的元类描述(在 第一部分第二到第四部分)。

撰写回答