1

For the sake of learning, I'm trying to chain decorators that are defined by classes. I read this question about decorators, which has a lot of good information about chaining them with functions. They also link to the documentation, but I'm trying to figure out simpler examples.

Basically, I'm trying to mimic similar behaviour using classes. Here is my first decorator definition, which works perfectly.

class square_result(object):
    def __init__(self, f):
        pass

def __call__(self, x, y):
        return (x+y)**2

@square_result
def add_two_numbers(x, y):
    return x + y

print(add_two_numbers(2,5)) #Outputs 49, as expected

Then, I add another decorator to create this code snippet:

class square_result(object):
    def __init__(self, f):
        pass

    def __call__(self, x, y):
        return (x+y)**2

class append_abc(object):
    def __init__(self, f):
        pass

    def __call__(self, *args):
        return str(*args) + "abc"

@append_abc
@square_result
def add_two_numbers(x, y):
    return x + y

print(add_two_numbers(2,5))
#Ideally, this should print "49abc" but prints "(2,5)abc" instead

what is the proper way of doing this? I guess what I want to do is create a decorator in the form of a class that takes the output of the function it decorates (in this case square_result) and appends "abc" to it.

I know that when I add a decorator to my code, the add_two_numbers() function is compiled and that function object is passed to the square_result class, which does something to create a function-like object which is substituted for the original add_two_numbers(). However, I'm not sure how to chain this.

Community
  • 1
  • 1
Ricardo Altamirano
  • 14,650
  • 21
  • 72
  • 105

2 Answers2

2

This does what you want:

class square_result(object):
    def __init__(self, f):
        pass

    def __call__(self, x, y):
        return (x+y)**2

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

    def __call__(self, *args):
        return str(self.f(*args)) + "abc"

@append_abc
@square_result
def add_two_numbers(x, y):
    return x + y

print(add_two_numbers(2,5))

You need to actually run the inner function in the decorator if you want to use its output in the result of the decorator.

I didn't edit your first decorator, as it does what you want, but it actually isn't useful as a decorator. Since its output is not related in any way to the function it's decorating, it's just replacing the function. If you wanted to replace the function with that class, the way to do it would be

class square_result(object):
    def __call__(self, x, y):
        return (x+y)**2

# this has no effect at all after the reassignment    
def add_two_numbers(x, y):
    return x + y

add_two_numbers = square_result()

PEP8 also suggests CamelCase for your class names (SquareResult).

agf
  • 171,228
  • 44
  • 289
  • 238
1

square_result doesn't "decorate" the add_two_numbers result, but overrides it (doing the addition as well as the squaring). It should instead treat the decorated function the same way that append_abc does, by storing it and then making use of the decorated function in its own implementation. Thus:

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

    def __call__(self, *args):
        return self.function(*args)**2

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

    def __call__(self, *args):
        return str(self.function(*args)) + "abc"

@append_abc
@square_result
def add_two_numbers(x, y):
    return x + y

print(add_two_numbers(2,5))
Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153