2011-09-28 36 views
2

類型A的對象和是否有編程方式包裝類對象的方法?Python內省:方法的自動換行

鑑於

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

有沒有辦法做到創建AB.f0 & B.f1通過反射/檢查?

+2

是的,使用類裝飾器。 –

+1

但是,對於我的這種看起來有點像繼承,是你正在尋找還是有隱藏在你的問題背後的具體用法? – aweis

+2

一個類裝飾器或(對於Python 2.x)一個元類將允許您在B編譯時檢查B的字典,並使用包裝器來替換其中的方法定義。 – millimoose

回答

4

如果你想通過調用預定義類A一個函數來創建B類,你可以簡單地做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 

你想要做什麼,其實通常由元類完成,這是一個類的實例是一個類:這是一種基於其解析的Python代碼動態構建B類的方法(類A,代碼在問題中)。關於這方面的更多信息可以在Chris的Wiki中給出的關於元類的簡短描述中找到(在part 1parts 2-4中)。

+0

我想如果這是你所做的一切,不妨在類decorator/wrapper的'__init__'中做,就像在另一個答案中一樣。不需要引入元類。 – agf

+0

但是,這並不包裝A實例本身。如果他從外部來源獲得self.a,他可能不想在B中重新實現A的方法。 –

+0

好吧,現在wrap_class會進行委派。它會更簡單,沒有它創建(深層複製)一個新的類,但只是裝飾它,使用@wrap_class/class B(object)... –

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

第一個「老派」解決方案非常低效:每次調用函數時都會創建'wrapper()'函數。第二種解決方案失敗了,因爲它將'__class__'屬性封裝到一個函數中(而不是一個新樣式的類),但是這可以通過執行與'callable()'不同的測試來解決(比如'isinstance(attr,types)。 MethodType)')。 – EOL

2

元類是一種選擇,但一般很難理解。如果在簡單情況下不需要,則反映太多 ,因爲很容易捕獲太多(內部)功能。如果包裹的功能是穩定組已知和B可能獲得其他的功能,你可以委派明確功能作用,同時確保自己的錯誤處理代碼在一個地方:

class B(object): 

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

你可能做的任務在一個循環如果它們很多,則使用getattr/setattr。

errorhandler函數將需要返回一個函數,該函數用 錯誤處理代碼包裝其參數。

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

您還可以使用的ErrorHandler作爲不委託給一個實例新功能的裝飾。

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

該解決方案保持乙簡單,它是非常明確發生了什麼事情。