将类的所有可能的方法调用包装在 try/except 块中

Posted

技术标签:

【中文标题】将类的所有可能的方法调用包装在 try/except 块中【英文标题】:Wrapping all possible method calls of a class in a try/except block 【发布时间】:2016-08-13 16:25:24 【问题描述】:

我正在尝试将现有类(不是我创建的)的所有方法包装到 try/except 套件中。它可以是任何类,但我将在此处使用 pandas.DataFrame 类作为实际示例。

所以如果调用的方法成功了,我们就继续前进。但是如果它应该生成一个异常,它会被附加到一个列表中以供以后检查/发现(尽管为了简单起见,下面的示例只是发出一个打印语句)。

(请注意,调用实例上的方法时可能发生的与数据相关的异常类型尚不清楚;这就是本练习的原因:发现)。

这个post 非常有帮助(尤其是@martineau Python-3 的答案),但我无法适应它。下面,我预计对(包装的)info() 方法的第二次调用会发出打印输出,但遗憾的是,它没有。

#!/usr/bin/env python3

import functools, types, pandas

def method_wrapper(method):
    @functools.wraps(method)
    def wrapper(*args, **kwargs): #Note: args[0] points to 'self'.
        try:
            print('Calling: .()... '.format(args[0].__class__.__name__,
                                                method.__name__))
            return method(*args, **kwargs)
        except Exception:
            print('Exception: %r' % sys.exc_info()) # Something trivial.
            #<Actual code would append that exception info to a list>.
    return wrapper


class MetaClass(type):
    def __new__(mcs, class_name, base_classes, classDict):
        newClassDict = 
        for attributeName, attribute in classDict.items():
            if type(attribute) == types.FunctionType: # Replace it with a
                attribute = method_wrapper(attribute) # decorated version.
            newClassDict[attributeName] = attribute
        return type.__new__(mcs, class_name, base_classes, newClassDict)

class WrappedDataFrame2(MetaClass('WrappedDataFrame',
                                  (pandas.DataFrame, object,), ),
                                  metaclass=type):
    pass

print('Unwrapped pandas.DataFrame().info():')
pandas.DataFrame().info()

print('\n\nWrapped pandas.DataFrame().info():')
WrappedDataFrame2().info()
print()

这个输出:

Unwrapped pandas.DataFrame().info():
<class 'pandas.core.frame.DataFrame'>
Index: 0 entries
Empty DataFrame

Wrapped pandas.DataFrame().info():   <-- Missing print statement after this line.
<class '__main__.WrappedDataFrame2'>
Index: 0 entries
Empty WrappedDataFrame2

总之,...

>>> unwrapped_object.someMethod(...)
# Should be mirrored by ...

>>> wrapping_object.someMethod(...)
# Including signature, docstring, etc. (i.e. all attributes); except that it
# executes inside a try/except suite (so I can catch exceptions generically).

【问题讨论】:

P.S.如果我延迟回复 cmets 或 answer,可能是因为我正在尝试建议或尝试先理解它。 =:) 【参考方案1】:

好久不见。 ;-) 其实这么久了你可能已经不在乎了,但万一你(或其他人)...

这是我认为会做你想做的事。我以前从未回答过你的问题,因为我的系统上没有安装pandas。但是,今天我决定看看是否有没有它的解决方法,并创建了一个简单的虚拟模块来模拟它(仅在我需要的情况下)。这是里面唯一的东西:

mockpandas.py:

""" Fake pandas module. """

class DataFrame:
    def info(self):
        print('pandas.DataFrame.info() called')
        raise RuntimeError('Exception raised')

下面的代码似乎可以通过实现@Blckknght 的迭代 MRO 的建议来满足您的需求,但忽略了他的回答中指出的这样做可能会产生的限制)。它并不漂亮,但正如我所说,它似乎至少可以与我创建的模拟 pandas 库一起使用。

import functools
import mockpandas as pandas  # mock the library
import sys
import traceback
import types

def method_wrapper(method):
    @functools.wraps(method)
    def wrapper(*args, **kwargs): # Note: args[0] points to 'self'.
        try:
            print('Calling: .()... '.format(args[0].__class__.__name__,
                                                method.__name__))
            return method(*args, **kwargs)
        except Exception:
            print('An exception occurred in the wrapped method .()'.format(
                    args[0].__class__.__name__, method.__name__))
            traceback.print_exc(file=sys.stdout)
            # (Actual code would append that exception info to a list)

    return wrapper

class MetaClass(type):
    def __new__(meta, class_name, base_classes, classDict):
        """ See if any of the base classes were created by with_metaclass() function. """
        marker = None
        for base in base_classes:
            if hasattr(base, '_marker'):
                marker = getattr(base, '_marker')  # remember class name of temp base class
                break  # quit looking

        if class_name == marker:  # temporary base class being created by with_metaclass()?
            return  type.__new__(meta, class_name, base_classes, classDict)

        # Temporarily create an unmodified version of class so it's MRO can be used below.
        TempClass = type.__new__(meta, 'TempClass', base_classes, classDict)

        newClassDict = 
        for cls in TempClass.mro():
            for attributeName, attribute in cls.__dict__.items():
                if isinstance(attribute, types.FunctionType):
                    # Convert it to a decorated version.
                    attribute = method_wrapper(attribute)
                    newClassDict[attributeName] = attribute

        return type.__new__(meta, class_name, base_classes, newClassDict)

def with_metaclass(meta, classname, bases):
    """ Create a class with the supplied bases and metaclass, that has been tagged with a
        special '_marker' attribute.
    """
    return type.__new__(meta, classname, bases, '_marker': classname)

class WrappedDataFrame2(
        with_metaclass(MetaClass, 'WrappedDataFrame', (pandas.DataFrame, object))):
    pass

print('Unwrapped pandas.DataFrame().info():')
try:
    pandas.DataFrame().info()
except RuntimeError:
    print('  RuntimeError exception was raised as expected')

print('\n\nWrapped pandas.DataFrame().info():')
WrappedDataFrame2().info()

输出:

Unwrapped pandas.DataFrame().info():
pandas.DataFrame.info() called
  RuntimeError exception was raised as expected


Wrapped pandas.DataFrame().info():
Calling: WrappedDataFrame2.info()...
pandas.DataFrame.info() called
An exception occurred in the wrapped method WrappedDataFrame2.info()
Traceback (most recent call last):
  File "test.py", line 16, in wrapper
    return method(*args, **kwargs)
  File "mockpandas.py", line 9, in info
    raise RuntimeError('Exception raised')
RuntimeError: Exception raised

如上所示,method_wrapper() 装饰版本被包装类的方法使用。

【讨论】:

【参考方案2】:

您的元类仅将您的装饰器应用于作为它的实例的类中定义的方法。它不会修饰继承的方法,因为它们不在 classDict 中。

我不确定是否有使它起作用的好方法。您可以尝试遍历 MRO 并包装所有继承的方法以及您自己的方法,但我怀疑如果在您开始使用 MetaClass 后存在多个继承级别,您会遇到麻烦(因为每个级别都会装饰已经前一个类的修饰方法)。

【讨论】:

猴子直接修补 pd.DataFrame 是解决方案之一吗? 猴子补丁可能是一种选择。我也写了 Pandas 的作者来了解他的观点(在 *** 之外)。也许他有一些想法,当我与他联系时,我会很高兴地分享。试图不走太远的人迹罕至的道路,但。 =:) 感谢@Blckknght 的反馈。会更多地思考你的答案。 嗨@Blckknght。感谢您提供有趣的 MRO 方法答案。关于您提到的有效问题('多级继承),您的意思是将来可能对“MetaClass”和/或“WrappedDataFrame2”进行子类化吗?还是其他什么副作用? (顺便说一句,这里的“MetaClass”是用于这种一次性/专有的私人用途)。谢谢。

以上是关于将类的所有可能的方法调用包装在 try/except 块中的主要内容,如果未能解决你的问题,请参考以下文章

python(18):类(2)

将类从可执行文件导出到 dll

Java——包装类(Wrapper)

13-Object&包装类

java的反射机制

在C ++ / CLI中通过包装器(派生的托管类)调用派生本机类的重写方法