【问题标题】:Wrap every function of every class of a module包装模块的每个类的每个函数
【发布时间】:2020-04-22 07:48:46
【问题描述】:

目标

包装gspread模块的每个类的每个函数。

我知道有无数关于这个主题的帖子,并且最一致地指示使用装饰器。 我对装饰器不太熟悉,感觉这种方法并不像我希望的那样无缝。可能我没听懂。

但是,我发现这个answer“感觉”就像我要找的东西。

(差)尝试

import gspread
from oauth2client.service_account import ServiceAccountCredentials
import os
import inspect

class GoogleSheetAPI:
    def __init__(self):
        f = os.path.join(os.path.dirname(__file__), 'credentials.json')
        os.environ.setdefault('GOOGLE_APPLICATION_CREDENTIALS', f)
        scope = ['https://spreadsheets.google.com/feeds',
                 'https://www.googleapis.com/auth/drive']
        credentials = ServiceAccountCredentials.from_json_keyfile_name(f, scope)
        self.client = gspread.authorize(credentials)
        self.client.login()

def SafeCall(f):
    try:
        print 'before call'
        f()
        print 'after call'
    except:
        print 'exception caught'
        return None

for class_name, c in inspect.getmembers(gspread, inspect.isclass):
    for method_name, f in inspect.getmembers(c, inspect.ismethod):            
        setattr(c, f, SafeCall(f)) # TypeError: attribute name must be string, not 'instancemethod'

g = GoogleSheetAPI()
spreadsheet = g.client.open_by_key('<ID>') # calls a function in gspread.Client
worksheet = spreadsheet.get_worksheet(0)  # calls a function in gspread.Spreadsheet
worksheet.add_rows(['key','value'])  # calls a function in gspread.Worksheet

注意事项

  1. 当我使用“无缝”这个词时,我的意思是考虑到我的代码对许多 gspread 函数有很多调用,我希望尽可能少地进行更改。使用 inspect/setattr 似乎是完美/无缝的技巧。

【问题讨论】:

    标签: python-2.7


    【解决方案1】:

    实际上,您的代码存在三个明显的问题。

    第一个是 TypeError - 这很容易解决 FWIW: 作为错误消息(由setattr() 提出,“属性名称必须是字符串,而不是'instancemethod'”。你确实在尝试使用f(方法本身)而不是method_name。你想要的当然是:

    setattr(c, method_name, SafeCall(f))
    

    第二个问题是您的 SafeCall“装饰器”不是装饰器。装饰器(好吧,至少是你想要的那种装饰器)返回一个包装原始函数的函数,你当前的实现只是调用原始函数。实际上,几乎SafeCall 应该实际返回。一个合适的装饰器的例子是:

    def decorator(func):
        def wrapper(*args, **kw):
            print("before calling {}".format(func))
            result = func(*args, **kw)
            print("after calling {}".format(func))
            return result
        return wrapper
    

    最后,第三个明显的问题在这里:

    except:
        print 'exception caught'
        return None
    

    你当然不想要这个。这个

    1/ 绝对会捕获所有内容(包括 SysExit,这是 Python 在 sys.exit() 调用时引发的,StopIteration 是迭代器发出信号耗尽的信号),

    2/ 丢弃所有非常有用的调试信息 - 使得无法诊断实际出了什么问题

    3/ 返回一些可能无法使用的东西,因此您必须测试每个方法调用的返回值,并且由于您不知道出了什么问题,因此您将无法处理该问题,除非打印“哎呀,出了点问题,但不要问我是什么,在哪里,为什么”并退出程序,这绝对比让异常传播更好 - 程序在这两种情况下都会崩溃,但至少如果你离开仅例外,您就会对导致问题的原因有一些提示。

    4/ 或者,更糟糕的是,为方法返回一个有效的返回值(是的,很多方法被设计为改变状态并返回None),所以你甚至不会知道出了什么问题并愉快地继续执行- 这是产生不正确结果和损坏数据的可靠方法。

    5/ 更不用说您以这种方式装饰的方法很可能会相互调用并在内部使用(预期的)异常(使用 proper 异常处理),因此您实际上是在引入错误在其他工作(或大部分工作)库中。

    IOW,这可能是你能想到的最糟糕的反模式......

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-11-20
      • 1970-01-01
      • 2023-02-21
      • 2020-09-17
      • 1970-01-01
      • 2013-12-10
      • 1970-01-01
      相关资源
      最近更新 更多