Как использовать декораторы в переопределенных методах класса - PullRequest
0 голосов
/ 24 января 2019

У меня есть собственный класс с несколькими методами, которые все возвращают код. Мне нужна стандартная логика, которая проверяет возвращаемый код по списку допустимых кодов для этого метода и выдает ошибку, если она не ожидалась.

Я думал, что хороший способ добиться этого - использовать декоратор:

from functools import wraps

def expected_codes(codes):
    def decorator(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            code = f(*args, **kwargs)
            if code not in codes:
                raise Exception(f"{code} not allowed!")
            else:
                return code
        return wrapper
    return decorator

тогда у меня есть такой класс:

class MyClass:
    @expected_codes(["200"])
    def return_200_code(self):
        return "200"

    @expected_codes(["300"])
    def return_300_code(self):
        return "301" # Exception: 301 not allowed!

Это прекрасно работает, однако, если я переопределить базовый класс:

class MyNewClass:
    @expected_codes(["300", "301"])
    def return_300_code(self):
        return super().return_300_code()  # Exception: 301 not allowed!

Я бы ожидал, что вышеуказанный переопределенный метод будет возвращаться правильно, а не вызывать исключение из-за переопределенного декоратора.

Исходя из того, что я собрал во время чтения, мой желаемый подход не сработает, потому что декоратор оценивается при определении класса - однако я удивлен, что нет способа достичь того, чего я хотел. Это все в контексте приложения Django, и я подумал, что декоратор Djangos method_decorator мог бы позаботиться об этом за меня, но я думаю, что у меня есть фундаментальное недопонимание того, как это работает.

1 Ответ

0 голосов
/ 24 января 2019

TL; DR

Используйте атрибут __wrapped__, чтобы игнорировать декоратор родителя:

class MyNewClass(MyClass):
    @expected_codes(["300", "301"])
    def return_300_code(self):
        return super().return_300_code.__wrapped__(self) # No exception raised

Объяснение

Синтаксис @decorator эквивалентен:

def f():
    pass
f = decorator(f)

Поэтому вы можете складывать декораторы:

def decorator(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        print(f"Calling {f.__name__}")
        f(*args, **kwargs)
    return wrapper

@decorator
def f():
    print("Hi!")

@decorator
def g():
    f()  
g()

#Calling g
#Calling f
#Hi!

Но если вы хотите избежать суммирования, атрибут __wrapped__ - ваш друг:

@decorator
def g():
    f.__wrapped__()
g()

#Calling g
#Hi!

Короче говоря, если вы вызываете один из методов декорированного родителя в декорированном методе дочернего класса, декораторы будут складываться, а не переопределять друг друга.

Поэтому, когда вы вызываете super().return_300_code(), вы вызываете декорированныйметод родительского класса, который не принимает 301 в качестве допустимого кода и вызовет свое собственное исключение.

Если вы хотите повторно использовать метод исходного родителя, тот, который просто возвращает 301 без проверки, вы можете использовать атрибут __wrapped__, который дает доступ к исходной функции (до ее оформления):

class MyNewClass(MyClass):
    @expected_codes(["300", "301"])
    def return_300_code(self):
        return super().return_300_code.__wrapped__(self) # No exception raised
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...