0

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())
Dhiwakar Ravikumar
  • 1,983
  • 2
  • 21
  • 36
  • `full_name` is not passed as an argument to `designation.__init__`; it's passed as an argument to `designation.__call__` once the instance is created. – chepner Nov 29 '20 at 20:12
  • I don't understand what problem you want to solve using the decorator. But maybe you will find the answers [here](https://stackoverflow.com/questions/739654/how-to-make-function-decorators-and-chain-them-together) useful, although it is a different question. – Karl Knechtel Nov 29 '20 at 20:18
  • @KarlKnechtel, I am trying to understand how to decorate instance methods by writing an example myself. I used this https://medium.com/@vadimpushtaev/decorator-inside-python-class-1e74d23107f6 for reference but I'm having a hard time with implementing it. – Dhiwakar Ravikumar Nov 29 '20 at 20:19
  • Interesting question. A quick search gave me this link: https://towardsdatascience.com/using-class-decorators-in-python-2807ef52d273 – brillenheini Nov 29 '20 at 20:21
  • @ThomasKlinger, can you please help me with the solution in this case ? The link illustrates using a class to decorate a method, I want to decorate an instance method with an inner class i.e. as suggested in the final paragraph of the link I shared. – Dhiwakar Ravikumar Nov 29 '20 at 20:28
  • @chepner, I'm not following, can you please help me implement the decorator by using an inner class for the class DecoratorTest ? – Dhiwakar Ravikumar Nov 29 '20 at 21:09

2 Answers2

1

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

brillenheini
  • 793
  • 7
  • 22
1

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
mrgreytop
  • 74
  • 4
  • thanks, if I were to make this a property, i.e. decorate the already decorated def full_name with (property) property, I noticed that in every call to full_name, the object of DecoratorTest is passed whereas removing (decorator) property does not lead to this, any idea why this difference ? My understanding is that (decorator) property is called after it has been decorated with (property) designation. – Dhiwakar Ravikumar Nov 29 '20 at 22:11
  • mrgreytop: I never said that my answer is fully armed. :) @dhiwakar-ravikumar could you please unmark my answer as solution? Thx – brillenheini Nov 29 '20 at 22:30
  • When going through this, I noticed a difference -> https://stackoverflow.com/questions/65065914/decorating-with-property-results-in-additional-argument-being-passed, any idea why this is the case ? I'm refreshing my understanding of this python concept and things like this get me. – Dhiwakar Ravikumar Nov 29 '20 at 22:34
  • @mrgreytop, again, I'm trying to understand so forgive me if my comment comes across as rude. In the other answer, the function makes use of __init__() to store the function that is being decorated so it can be used in __call__, whereas in yours you are directly decorating __call__() thus being the more direct approach, correct ? – Dhiwakar Ravikumar Nov 29 '20 at 22:40
  • @DhiwakarRavikumar Your last comment is correct. Sorry, I have no clue why you get that extra arg in your other question, something to do with using the designation __init perhaps? I have also added how to add the \@property. – mrgreytop Nov 30 '20 at 20:33
  • @ThomasKlinger appreciate that your answer wasn't fully tested, sorry if I was in anyway rude, just wanted to point out that it doesn't quite work how you would expect. – mrgreytop Nov 30 '20 at 20:38
  • @DhiwakarRavikumar Don't worry, you haven't been rude. :) Everything's ok. I justed to point out that my answer is not correct, that's why it is important to unmark it. – brillenheini Dec 02 '20 at 09:35