定义或记录__getattr__处理的调用的正确方法是什么?

时间:2016-02-08 19:58:06

标签: python autocomplete documentation getattr

我有一个类的工作是包装另一个类(我无法控制的代码),拦截对包装类的所有调用,执行一些逻辑,并将调用传递给底层类。这是一个例子:

class GithubRepository(object):

    def get_commit(self, sha):
        return 'Commit {}'.format(sha)

    def get_contributors(self):
        return ['bobbytables']


class LoggingGithubRepositoryWrapper(object):

    def __init__(self, github_repository):
        self._github_repository = github_repository

    def __getattr__(self, name):
        base_func = getattr(self._github_repository, name)

        def log_wrap(*args, **kwargs):
            print "Calling {}".format(name)
            return base_func(*args, **kwargs)

        return log_wrap

if __name__ == '__main__':

    git_client = LoggingGithubRepositoryWrapper(GithubRepository())

    print git_client.get_commit('abcdef1245')
    print git_client.get_contributors()

正如您所看到的,我这样做的方法是在包装类上实现__getattr__并委托给底层类。这种方法的缺点是LoggingGithubRepositoryWrapper的用户不知道底层GithubRepository实际上具有哪些属性/方法。

这引出了我的问题:有没有办法定义或记录__getattr__处理的调用?理想情况下,我希望能够在{{1}上自动完成并提供支持的方法列表。感谢您的帮助!

1 个答案:

答案 0 :(得分:1)

您可以通过几种不同的方式执行此操作,但它们不会涉及使用__getattr__

您真正需要做的是动态创建您的类,或者至少在您的类上动态创建包装函数。在python中有几种方法可以做到这一点。

您可以使用type()或元类构建类定义,或使用__new__方法在类实例化上构建它。

每次拨打LoggingGithubRepositoryWrapper()时,都会调用__new__方法。在这里,它查看github_repository参数的所有属性,并查找所有非私有方法。然后,它在实例化的LoggingGithubRepositoryWrapper类实例上创建一个函数,该实例在日志语句中包装repo调用。

最后,它传回修改后的类实例。然后调用__init__

from types import MethodType


class LoggingGithubRepositoryWrapper(object):

    def __new__(cls, github_repository):
        self = super(LoggingGithubRepositoryWrapper, cls).__new__(cls)
        for name in dir(github_repository):
            if name.startswith('__'):
                continue
            func = getattr(github_repository, name)            
            if isinstance(func, MethodType):                
                setattr(self, name, cls.log_wrap(func))
        return self

    @staticmethod
    def log_wrap(func):
        def wrap(*args, **kwargs):
            print 'Calling {0}'.format(func.__name__)
            return func(*args, **kwargs)
        return wrap

    def __init__(self, github_repository):
        ... # this is all the same