装饰器是否可以返回一个与包装函数具有相同参数的函数?

时间:2011-08-27 01:59:36

标签: python decorator

所以我正在为自己的方便编写一个简单的堆数据结构,并意识到要正确地执行它,我需要检查是否有许多参数是可以清除的,以便heapq操作正常工作。

import heapq


def hashable(func):
    def hidden(self, item):
        try:
            hash(item)
            func(self, item)
        except TypeError as e:
            raise e
    hidden.__doc__ = func.__doc__
    return hidden


class heap(object):
    def __init__(self, heap=[]):
        self.heap = heap
        heapq.heapify(heap)

    def pop(self):
        '''
        Pop and return the smallest item from the heap, maintaining the heap
        invariant. If the heap is empty, IndexError is raised.
        '''
        return heapq.heappop(self.heap)

    @hashable
    def push(self, item):
        '''
        Push the value item onto the heap, maintaining the heap invariant.
        '''
        return heapq.heappush(self.heap, item)

    @hashable
    def pushpop(self, item):
        '''
        Push item on the heap, then pop and return the smallest item from
        the heap.

        The combined actions runs more efficiently than heappush()
        followed by a separate called to heappop().'''
        heapq.heappushpop(self.heap, item)

    @hashable
    def poppush(self, item):
        '''
        Pop and return the smallest item from the heap, then push item on
        the heap.

        The combined actions runs more efficiently than heappop()
        followed by a separate called to heappush().'''
        heapq.heapreplace(self.heap, item)

    def __setitem__(self, i, y):
        self.heap[self.heap.index(i)] = y
        heapq.heapify(self.heap)

    def __len__(self):
        return len(self.heap)

    def __iter__(self):
        while self.heap:
            yield self.pop()

我遇到的问题是 setitem 。虽然 setitem 也要求'y'可以清洗,如果我用hashable装饰它,它只能用一个参数。

显而易见的解决方案就是改变hashable的隐藏功能来接受'self'和'* args',但是我很愿意使用这样的解决方案,因为它不漂亮并且只会使代码复杂化。

我的问题是,是否有可能重写代码以便隐藏采用传递给装饰器的函数的参数?

3 个答案:

答案 0 :(得分:3)

为什么让hashable成为装饰者?为什么不让它成为一个你可以调用的函数,它会正常引发异常,然后只是在函数体中调用它,将它传递给你要检查的东西?

事实上,从您编写装饰器的方式来判断,hash()已经完全没有了?为什么不,而不是这个:

@hashable
def push(self, item):
    '''
    Push the value item onto the heap, maintaining the heap invariant.
    '''
    return heapq.heappush(self.heap, item)

写这个?

def push(self, item):
    '''
    Push the value item onto the heap, maintaining the heap invariant.
    '''
    hash(item)
    return heapq.heappush(self.heap, item)

如果您这样做,那么它会解决您的__setitem__问题:

def __setitem__(self, i, y):
    hash(i), hash(y)
    self.heap[self.heap.index(i)] = y
    heapq.heapify(self.heap)

也就是说,关于“使用相同的args返回一个函数”的问题 - 完全 *args**kwargs语法是为什么设计的,所以我'我不确定你为什么认为它“不漂亮”或“使代码复杂化”。

答案 1 :(得分:1)

首先,我会采取另一种方式:

def hashable(func):
    def hidden(self, item):
        try:
            hash(item)
        except TypeError as e:
            raise e
        else:
            return func(self, item) # return might be important!
    hidden.__doc__ = func.__doc__
    return hidden

它确实完全相同(但只是因为except套件“没有”),但它向人类读者显示了不同的意图:你对func()中发生的异常不感兴趣。 (或者甚至省略try ... except ... else而只是做

hash(item)
return func(...)

这也会引起错误,并在成功时返回。

此外,包装器应该return func()的返回值才能真正灵活。

然后你可以写一个hashable2(),它用2个参数做同样的事情。或者你甚至可以写一个“间接装饰者”,我。即返回装饰器的函数。所以你可以使用

@hashable(2)
def ...

甚至

@hashable(1, 2)
def ...

以指示应该可以清除的参数索引。

答案 2 :(得分:0)

我同意Amber的观点,在这种情况下,直接使用hash可能更简单。

但是对于您的问题的一般情况,请查看wraps标准库模块中的functools装饰器。你可以这样做:

def hashable(func):
    @functools.wraps(func)
    def hidden(self, item):
        ...
    return hidden

这意味着从func复制返回的funcion的名称,docstring和模块。这可能不足以让标签完成,但它至少有助于包装函数看起来更像原始。