Dhiwakar Ravikumar
Dhiwakar Ravikumar

Reputation: 2217

Decorating A Property

I am trying to decorate an instance method which prints the full name with another method that simply adds a designation and I've managed to figure out this much.

class DecoratorTest:

    def __init__(self):
        self._first_name = ['CHARLES', 'WILIAM', 'ENID']
        self._last_name = ['DICKENS', 'SHAKESPEARE', 'BLYTON']
        self._full_name = None

    class designation(object):
        def __init__(self, decorated):
            self._decorated = decorated

        def __call__(self, *args, **kwargs):
            self._full_name = "MR" + self._decorated()
            return self._full_name

    @designation()
    def full_name(self):
        if not self._full_name:
            self._full_name = random.choices(self._first_name) + random.choices(self._last_name)
        return self._full_name

a = DecoratorTest()
print(a.full_name())

With some reading, I figured out that the decorator @designation cannot be an instance method since the first argument would be self and not the function that needs to be decorated so any call to it would immediately fail which is why I've specified the decorator designation as a class hoping that the redefined descriptor __call__() would help here.

What I'm unable to figure out is how to proceed further. I keep running into various errors and no amount of trial and error is getting me the solution.

EDIT

After some work, I managed to get this to work albeit after ditching the class decorator, the problem now however is that it is an instance method which brings up the issue pointed out in the blog link I mentioned in my comment i.e. in this link https://medium.com/@vadimpushtaev/decorator-inside-python-class-1e74d23107f6

How can I solve this ?

import random

class DecoratorTest:

    def __init__(self):
        self._first_name = ['CHARLES', 'WILIAM', 'ENID']
        self._last_name = ['DICKENS', 'SHAKESPEARE', 'BLYTON']
        self._full_name = None

    def designation(func):
        def wrapper(self):
            full_name = "MR" + func(self)
            return full_name
        return wrapper

    @designation
    def full_name(self):
        if not self._full_name:
            self._full_name = str(random.choices(self._first_name)) + str(random.choices(self._last_name))
        return self._full_name

a = DecoratorTest()
print(a.full_name())

Upvotes: 0

Views: 170

Answers (2)

mrgreytop
mrgreytop

Reputation: 84

TLDR (go to bottom)

First lets remind ourselves what decorators do.

They are callables/functions which take a function as an input and return a new function (decorated).

So that means that we can decorate a function like this.

def return_hello():
    return "hello "

def multiply_by_5(func):
    def new_func(*args,**kwargs):
        return func(*args, **kwargs) * 5

 multiply_by_5(return_hello)()

Or using the @ syntax

@multiply_by_5
def return_hello():
    return "hello "

return_hello()

So, in your example, you have decorated your method with

@designation()

Which is emulating a callable via the __call__ method (see docs). This means that the __call__ method is passed the function, (ie the __call__ method is actually the decorator here).

Therefore you can fix the code like this:

...
class designation():

    def __call__(self, func):
        def new_func(*n_args, **n_kwargs):
            return "MR" + func(*n_args,**n_kwargs)
        return new_func

@designation()
def full_name(self):
    ...

EDIT 2: you can also add the property decorator like this and it will work.

@property
@designation()
def full_name(self):
    ...

EDIT 3: you can acheive all of this with a simple function-based decorator as well. So unless you really need your decorator to be a class then I would recomend this instead.

def designation(func):
    def new_func(*args, **kwargs):
        name = func(*args, **kwargs)
        return "MR" + name
    return new_func

Upvotes: 1

brillenheini
brillenheini

Reputation: 983

I played around a little and came across this solution:

class DecoratorTest:

    def __init__(self):
        self._first_name = ['CHARLES', 'WILIAM', 'ENID']
        self._last_name = ['DICKENS', 'SHAKESPEARE', 'BLYTON']
        self._full_name = None

    class designation(object):
        def __init__(self, decorated, *args, **kwargs):
            print(str(*args))
            print(str(**kwargs))
            self._decorated = decorated

        def __call__(self, *args, **kwargs):
            full_name = "MR" + self._decorated(self)
            return full_name

    @designation
    def full_name(self):
        if not DecoratorTest()._full_name:
            full_name = ' '.join([random.choices(DecoratorTest()._first_name)[0], random.choices(DecoratorTest()._last_name)[0]])
        return full_name

Main problem is that you decorate an outer method with an inner decorator class which leads to the problem, that self from the outer method may point to the inner one. I used the classname itself to point to the correct values.

This is just a quick one, it may not be 100% accurate.

Regards, Thomas

Upvotes: 1

Related Questions