Python 装饰器用于实例方法

3 投票
3 回答
4897 浏览
提问于 2025-04-17 17:13

有人知道这段代码哪里出问题了吗?

def paginated_instance_method(default_page_size=25):
    def wrap(func):
        @functools.wraps(func)
        def inner(self, page=1, page_size=default_page_size, *args, **kwargs):
            objects = func(self=self, *args, **kwargs)
            return _paginate(objects, page, page_size)
        return inner
    return wrap

class Event(object):
    ...
    @paginated_instance_method
    def get_attending_users(self, *args, **kwargs):
        return User.objects.filter(pk__in=self.attending_list)

我遇到了以下错误:

    Traceback (most recent call last):
      File "<console>", line 1, in <module>
      File "/Users/zarathustra/Virtual_Envs/hinge/hinge_services/hinge/api/decorators.py", line 108, in wrap
        def inner(self, page=1, page_size=default_page_size, *args, **kwargs):
      File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/functools.py", line 33, in update_wrapper
        setattr(wrapper, attr, getattr(wrapped, attr))
    AttributeError: 'Event' object has no attribute '__name__'

我之所以觉得这段代码能正常运行,是因为我通过反复尝试,找到了下面这个装饰器,它在类方法中效果很好:

def paginated_class_method(default_page_size=25):
    def wrap(func):
        @functools.wraps(func)
        def inner(cls, page=1, page_size=default_page_size, *args, **kwargs):
            objects = func(cls=cls, *args, **kwargs)
            return _paginate(objects, page, page_size)
        return inner
    return wrap

相关问题:

3 个回答

0

这件事其实很简单,但乍一看可能会让人觉得有点棘手。你可以看看pep 318

@dec2
@dec1
def func(arg1, arg2, ...):
    pass

这段代码和下面的内容是一样的:

def func(arg1, arg2, ...):
    pass
func = dec2(dec1(func))

你有一个额外的包装器,它可以接收装饰器的参数,然后在被包装的函数中使用这些参数(这是一种叫做闭包设计模式的方式)。所以你的装饰器看起来会像这样:

@dec(arg=True)
def func(arg1, arg2, ...):
    pass

和下面的内容是一样的:

def func(arg1, arg2, ...):
    pass
func = dec(arg=True)(func)
3

paginated_instance_method 不是一个装饰器,它是一个函数,这个函数会 返回 一个装饰器。所以

@paginated_instance_method()
def get_attending_users(self, *args, **kwargs):

(注意括号。)

1

你的装饰器多了一层间接调用,这让事情变得复杂。当你这样做时:

@paginated_instance_method
def get_attending_users(self, *args, **kwargs):
    return User.objects.filter(pk__in=self.attending_list)

其实你是在做这个:

def get_attending_users(self, *args, **kwargs):
    return User.objects.filter(pk__in=self.attending_list)
get_attending_users = paginated_instance_method(get_attending_users)

这就是装饰器的作用。注意,paginated_instance_method 是用 get_attending_users 作为参数来调用的。这意味着在你的装饰器里,参数 default_page_size 被设置成了函数 get_attending_users。你的装饰器返回的是 wrap 函数,所以 get_attending_users 被赋值为这个 wrap 函数。

然后当你调用 Event().get_attending_users() 时,它实际上是在调用 wrap(self),这里的 self 是你的 Event 实例。wrap 期待的参数是一个函数,它试图返回一个新的函数来包装这个函数。但是传入的参数并不是一个函数,而是一个 Event 对象,所以 functools.wrap 在尝试包装时就失败了。

我猜测你想做的是这个:

@paginated_instance_method()
def get_attending_users(self, *args, **kwargs):
    return User.objects.filter(pk__in=self.attending_list)

也就是说,你希望 paginated_instance_method 能接受一个参数。但是即使你想使用这个参数的默认值,你仍然需要实际去 调用 paginated_instance_method。否则你只是把方法作为参数传递,这并不是 paginated_instance_method 所期待的。

之所以在类方法中“有效”,是因为类方法的第一个参数是类,而类(和实例不同)确实有一个 __name__ 属性。不过,我怀疑如果你进一步测试,你会发现它并没有真正达到你想要的效果,因为它仍然是在包装类,而不是方法。

撰写回答