2013-02-26 3 views
3

Кто-нибудь знает, что не так с этим кодом?Python decorator on instance method

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 
+0

Ваш декоратор не имеет смысла. Декоратор должен взять функцию в качестве аргумента, но ваш принимает только один аргумент, который, по-видимому, является числом ('default_page_size'). Вы намеревались украсить чем-то вроде '@paginated_instance_method (10)'? – BrenBarn

+0

@BrenBarn Да, я пытался это сделать. Я новичок в декораторах, но я добавлю пояснение, чтобы объяснить, почему я думал, что это сработает. –

+0

Можете ли вы показать, как вы использовали этот другой декоратор? – BrenBarn

ответ

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 установлен на функцию paginated_instance_method. Ваш декоратор возвращает функцию wrap, поэтому get_attending_users настроен на функцию wrap.

Затем, когда вы звоните Event().get_attending_users(), он вызывает wrap(self), где self - ваш экземпляр вашего события. 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.

Причина, по которой «работали» на classmethod является то, что classmethod принимает класс в качестве первого аргумента, и класс (в отличие от экземпляра) делает имеют атрибут __name__. Тем не менее, я подозреваю, что если вы проверите его дальше, вы обнаружите, что он действительно не делает то, что вы хотите, поскольку он все еще обертывает класс, а не метод.

+0

Он имел круглые скобки в другом использовании - это было важным отличием. Таким образом, он никогда не «работал» для paginated_class_method. Благодаря! –

3

paginated_instance_method не является декоратором, это функция, которую возвращает декоратор. Так

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

(Обратите внимание на круглые скобки.)

0

Это действительно просто, но сложно с первого взгляда. Посмотрите на pep 318.

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

Это эквивалентно:

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

У вас есть дополнительная обертка, которая принимает арг декоратора, чтобы использовать его в обернутых функций (closure design pattern). Так что ваш декоратор будет выглядеть следующим образом:

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

Эквивалент:

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