3

Я пытаюсь объединить все методы существующего класса (а не моего создания) в набор try/except. Это может быть любой класс, но я буду использовать класс pandas.DataFrame здесь как практический пример.Обертывание всех возможных вызовов метода класса в блоке try/except

Таким образом, если метод invoked успешно завершен, мы просто переходим. Но если он должен генерировать исключение, он добавляется к списку для последующей проверки/обнаружения (хотя приведенный ниже пример просто упрощает печать заявления печати).

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

Этот 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). 
+0

P.S. Если я задержу свой ответ на комментарии или ответ, возможно, это потому, что я сначала попытаюсь сделать это или попытаюсь понять это. = :) –

ответ

1

Ваш метаклассом применяется только ваш декоратор методов, определенных в классах, которые являются экземплярами него. Он не украшает унаследованные методы, поскольку они не находятся в classDict.

Я не уверен, что есть хороший способ заставить его работать. Вы можете попробовать выполнить итерацию через MRO и обернуть все унаследованные методы, а также свои собственные, но я подозреваю, что вы столкнулись с трудностями, если бы было несколько уровней наследования после того, как вы начнете использовать MetaClass (так как каждый уровень украсит уже украшенные методы предыдущего класса).

+1

- это исправление обезьяны прямо pd.DataFrame - одно из решений? –

+0

Модификация патчей может быть вариантом. Я написал автору Панды, чтобы получить его, тоже (вне StackOverflow). Возможно, у него есть некоторые мысли, и я с радостью поделюсь этим, когда я свяжусь с ним. Тем не менее, стараться не заходить слишком далеко от проторенного пути. = :) –

+0

Спасибо за отзыв @Blckknght. Поразмыслите над своим ответом. –

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() decoratored версия является используются методами обернутого класса.