使用getattr获取被描述符包装的方法

1 投票
2 回答
2400 浏览
提问于 2025-04-16 11:56

我有一个描述符,它可以在调用带有 @saveconfig 注解的方法后,保存我类里的配置:

class saveconfig(object):
    def __init__(self, f):
        self.f = f

    def __get__(self, instance, owner):
        def wrapper(*args):
            self.f(instance, *args)
            instance.cfg.write()
            instance.paramcfg.write()
        return wrapper

它的用法是这样的:

class pbtools():
    @saveconfig
    def getip(self, ip):
        (...)

这个功能运行得很好。现在我想用 getattr 来获取这个被装饰的方法。但是因为这个方法被描述符包装了,所以我得到的结果只是 wrapper

pbt = pbtools()
# results in "<function wrapper at 0x23cced8>:"
method = getattr(pbt, "getip")

我该如何通过 getattr 访问被包装的方法 getip,以便能通过它的名字来调用它呢?(当然,如果我能直接访问这个方法,我就不需要这样做了)。

额外说明:

这个脚本是从命令行调用的。我需要将一个命令行参数(字符串)映射到一个同名的方法上,以便调用它。此外,我还需要将命令行中的其他参数映射到这个方法的参数上,像这样:

pbtools getip 192.168.178.123 #-> calls getip with parameter 192.168.178.123

因为我无法获取原始方法,所以我不知道它有多少个参数来进行映射。我有几个方法都是这样被装饰的,因为我想把保存配置的这个跨切关注点从 pbtools 类的方法中分离出来。

2 个回答

1

首先,抱歉之前的评论是我的错。现在说到你最后的代码,它不会像你想的那样工作(因为你的方法不再被装饰器装饰了)。要理解装饰器,你需要看看这个:

class pbtools():
    @saveconfig
    def getip():
        (...)

这相当于:

class pbtools():

    def getip():
        (...)

    getip = saveconfig(getip)

在你最新的例子中,saveconfig 返回的是 self.f,而在我们的代码中,这等于 getip,所以在这种情况下,这段代码:

getip = saveconfig(getip)

相当于:

 getip = getip

所以基本上它什么都不做。

一个解决办法是把被装饰的函数保存在装饰器里面,像这样:

class saveconfig(object):
    def __init__(self, f):
        self.f = f

    def __get__(self, instance, owner):
        def wrapper(*args):
            self.f(instance, *args)
            instance.cfg.write()
            instance.paramcfg.write()

        wrapper.func = self.f    # Create a new attribute and assign it to the wrapped func.
        return wrapper

现在你可以:

class pbtools():
    @saveconfig
    def getip():
        print "hi"

pbt = pbtools()
method = getattr(pbt, "getip")
print method.func
# <function getip at 0x2363410>
method.func()
# hi

希望这能帮到你 :)

2

我还不太确定我完全理解你的问题。你说“因为我无法获取原始方法,所以不知道它有多少个参数来进行映射”,但其实你并不需要访问原始方法就可以使用可变数量的参数(因为装饰器里有*args)。你可以这样做:

import sys
cmd = sys.argv[1]
params = sys.argv[2:]

pbt = pbtools()

func = getattr(pbt, cmd)
func(*params)

你也可以稍微简化一下你的装饰器。其实它并不需要__get__这个机制。

import functools

def saveconfig(f):
    @functools.wraps(f)
    def wrapper(self, *args):
        f(self, *args)
        self.cfg.write()
        self.paramcfg.write()
    return wrapper

functools.wraps是一个辅助装饰器,它可以让你的包装函数看起来像原始函数(也就是说,它会复制函数的名称、文档字符串等信息)。这样做会让调试变得更简单,因为你能更清楚地知道异常是从哪里来的等等。

撰写回答