Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to persist decorators during inheritance?

I'm trying to write an abstract class with unimplemented methods, which will force the inheriting children to return a value of a specific type when they override the method (defined in the decorator).

When I use the code shown below, the child method does not call the decorator. I assume this is because the method is being overridden, which makes a lot of sense. My question is basically this: Is there a way to make a decorator persist through method overriding?

I'm not opposed to using something other than a decorator, but this was a solution that quickly popped to mind and I'm curios to know if there's any way to make it work.

In case going with a decorator is the correct and possible choice, it will look something like this:

def decorator(returntype):
    def real_decorator(function):
        def wrapper(*args, **kwargs):
            result = function(*args, **kwargs)
            if not type(result) == returntype:
                raise TypeError("Method must return {0}".format(returntype))
            else:
                return result
        return wrapper
    return real_decorator

I need my parent class to look similar to this one:

class Parent(ABC):
    @decorator(int)
    @abstractmethod
    def aye(self, a):
        raise NotImplementedError

And the child class will do something of this sort:

class Child(Parent):
    def aye(self, a):
        return a

I'd be more than happy to clarify my question better if needed, and thank you to everyone who takes the time to read this question in advance!

like image 377
hekzu Avatar asked Dec 23 '19 18:12

hekzu


People also ask

Are decorators polymorphism?

Decorators are a great example of polymorphism. Polymorphism is about how we can use different objects in the same place in our program, because they respond to the same set of messages.

Are decorators useful Python?

Needless to say, Python's decorators are incredibly useful. Not only can they be used to slow down the time it takes to write some code, but they can also be incredibly helpful at speeding up code. Not only are decorators incredibly useful when you find them about, but it is also a great idea to write your own.

When to use decorator?

You'll use a decorator when you need to change the behavior of a function without modifying the function itself. A few good examples are when you want to add logging, test performance, perform caching, verify permissions, and so on. You can also use one when you need to run the same code on multiple functions.

What is a decorator oop?

In object-oriented programming, the decorator pattern is a design pattern that allows behavior to be added to an individual object, dynamically, without affecting the behavior of other objects from the same class.


2 Answers

I'm not sure you can persists the effect of the decorator the way you want to, but you can still decorate a wrapper function in the Parent class which will not be an abstractmethod and let the children class implement the wrapped function like that :

from abc import ABC, abstractmethod

def decorator(returntype):
    def real_decorator(function):
        def wrapper(*args, **kwargs):
            result = function(*args, **kwargs)
            if not type(result) == returntype:
                raise TypeError("Method must return {0}".format(returntype))
            else:
                return result
        return wrapper
    return real_decorator

class Parent(ABC):
    @decorator(int)
    def aye(self, a):
        return self.impl_aye(a)

    @abstractmethod
    def impl_aye(self, a):
        raise NotImplementedError


class Child(Parent):
    def impl_aye(self, a):
        return a

There is also solutions to protect the aye method from the Parent class to be overridden if you need it, see this answer for example.

Otherwise if you want to use type hints and check your code with mypy (an optional static type checker for Python) you can get error message if you try to implement a child class with a return type incompatible with its parent class :

from abc import ABC, abstractmethod

class Parent(ABC):
    @abstractmethod
    def aye(self, a) -> int:
        raise NotImplementedError

class Child(Parent):
    def aye(self, a) -> str :
        return a

Output of mypy:

a.py:9: error: Return type "str" of "aye" incompatible with return type "int" in supertype "Parent"
Found 1 error in 1 file (checked 1 source file)
like image 79
mgc Avatar answered Oct 13 '22 17:10

mgc


IF you only want to enforce return type, here's my non-decorator suggestion (hadn't originally put it in as I am not fond of you-dont-want-to-do-this "answers" on SO).

class Parent:

    def aye(self, a):
        res = self._aye(a)
        if not isinstance(res, int):
            raise TypeError("result should be an int")
        return res

    def _aye(self, a):
        raise NotImplementedError()

class Child(Parent):

    def _aye(self, a):
        return 1
like image 26
JL Peyret Avatar answered Oct 13 '22 17:10

JL Peyret