Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing python decorators in a toy example

I have been trying to find a use case to learn decorators and I think I have found one which is relevant to me.

I am using the following codes.

In the file class1.py I have:

import pandas as pd, os

class myClass():
    def __init__(self):
        fnDone = f'C:\user1\Desktop\loc1\fn.csv'
        if os.path.exists(fnDone): return
        self.Fn1()
        pd.DataFrame({'Done': 1}, index=[0]).to_csv(fnDone)

    def Fn1(self):
        print('something')

if __name__ == '__main__':
    myClass()

In the file class2.py I have:

class myClassInAnotherFile():
    def __init__(self):
        fnDone = f'C:\user1\Desktop\loc2\fn.csv'
        if os.path.exists(fnDone): return
        self.Fn1()
        self.Fn2()
        pd.DataFrame({'Done': 1}, index=[0]).to_csv(fnDone)

    def Fn1(self):
        print('something')

    def Fn2(self):
        print('something else')

if __name__ == '__main__':
    myClassInAnotherFile('DoneFile12)

Is there a way to define a generic decorator code in another file called utilities.py so that I can do something of the following sort:

Desired in the file class1.py I have:

import pandas as pd, os

class myClass():
    def __init__(self):
        fnDone = f'C:\user1\Desktop\loc1\fn.csv'
        self.Fn1()
        pd.DataFrame({'Done': 1}, index=[0]).to_csv(fnDone)

    def Fn1(self):
        print('something')

if __name__ == '__main__':
    @myDecorator
    myClass()

In the file class2.py I have:

class myClassInAnotherFile():
    def __init__(self):
        fnDone = f'C:\user1\Desktop\loc2\fn.csv'
        self.Fn1()
        self.Fn2()
        pd.DataFrame({'Done': 1}, index=[0]).to_csv(fnDone)

    def Fn1(self):
        print('something')

    def Fn2(self):
        print('something else')

if __name__ == '__main__':
    @myDecorator
    myClassInAnotherFile()

Essentially mimicking the original behavior using a decorator.

Edit1: I am looking to extend the functionality of my class definitions. In the both original class definitions, I repeat the code which checks for fnDone file and if it is present, exits the class. Goal is to have a decorator which checks for the fnDone file and exits the class if it is present.

Edit2: I can do this as a function also but I am trying to learn how to extend functionality of a class or method using decorators.

Edit3: Does it make it easier if I have the following instead in class1.py:

def myClass():
    fnDone = f'C:\user1\Desktop\loc1\fn.csv'
    if os.path.exists(fnDone): return
    self.Fn1()
    pd.DataFrame({'Done': 1}, index=[0]).to_csv(fnDone)

def Fn1(self):
    print('something')

if __name__ == '__main__':
    myClass()

and class2.py as following:

def myClassInAnotherFile():
    fnDone = f'C:\user1\Desktop\loc2\fn.csv'
    if os.path.exists(fnDone): return
    self.Fn1()
    self.Fn2()
    pd.DataFrame({'Done': 1}, index=[0]).to_csv(fnDone)

def Fn1(self):
    print('something')

def Fn2(self):
    print('something else')

if __name__ == '__main__':
    myClassInAnotherFile('DoneFile12)
like image 324
Zanam Avatar asked Aug 05 '21 13:08

Zanam


3 Answers

Because fnDone is a local variable rather than a parameter, it makes using a decorator a bit awkward. If you modify the code slightly to pass in fnDone as a parameter, it makes using a decorator more of a viable option.

For example, you could make a decorator that wraps the constructor of an object, and checks if the file passed in exist or not:

import os.path
from functools import wraps

import pandas as pd

def check_file_exists(f):
    @wraps(f)
    def _inner(self, fn_done):
        if os.path.exists(fn_done):
            return
        f(self, fn_done)
    return _inner

class MyClass:
    @check_file_exists
    def __init__(self, fn_done) -> None:
        pd.DataFrame({'Done': 1}, index=[0]).to_csv(fn_done)

if __name__ == "__main__":
    MyClass("fn.csv")
like image 173
pigeonhands Avatar answered Oct 23 '22 18:10

pigeonhands


Here is a decorator checking if the file exists before running your code:

File: my_decorator.py

import os
import pandas as pd


def checkDoneDecorator(doneFilename):
    def _decorator(decorated):
        def _wrapper_function(*args, **kwargs):
            if os.path.exists(doneFilename):
                return

            try:
                result = decorated(*args, **kwargs)
            finally:
                pd.DataFrame({'Done': 1}, index=[0]).to_csv(doneFilename)
        return _wrapper_function
    return _decorator

File: class1.py

from my_decorator import checkDoneDecorator


@checkDoneDecorator(doneFilename='C:\user1\Desktop\loc1\fn.csv')
def myClass():
    Fn1()


def Fn1():
    print('something')


if __name__ == '__main__':
    myClass()

File: class2.py

from my_decorator import checkDoneDecorator


@checkDoneDecorator(doneFilename='C:\user1\Desktop\loc2\fn.csv')
def myClassInAnotherFile():
    Fn1()
    Fn2()


def Fn1():
    print('something')


def Fn2():
    print('something else')


if __name__ == '__main__':
    myClassInAnotherFile()

Some notes:

  • I used a decorator with argument doneFilename, which adds one more level of nested function than a simple decorator. You can see a detailed example here.
  • I also included the writing of the doneFilename inside the decorator, since the file check and the file writing are related. This is not mandatory though.
  • I removed the self arguments from your examples, as a class is not really needed in this example. If you really need a class, please don't put the decorator on __init__ and do it like this:
class myClass:

    @checkDoneDecorator(doneFilename='C:\user1\Desktop\loc1\fn.csv')
    def start(self):
        self.Fn1()

    def Fn1(self):
        print('something')

if __name__ == '__main__':
    myClass().start()
like image 34
duthils Avatar answered Oct 23 '22 17:10

duthils


I upvoted the answer given by @pigeonhands since you were primarily interested in in using a class. But this is how I would accomplish your goal using regular functions, which I think makes more sense. For the same reason offered by @pigeonhands, it makes sense to have the CSV filename be an argument to the myclass function as argument name fnDone:

import os.path
from functools import wraps

import pandas as pd

def myDecorator(func):
    @wraps(func)
    def wrapper(fnDone):
        if os.path.exists(fnDone):
            return
        func(fnDone)
    return wrapper

@myDecorator
def myClass(fnDone):
    Fn1()
    pd.DataFrame({'Done': 1}, index=[0]).to_csv(fnDone)

def Fn1():
    print('something')

if __name__ == '__main__':
    myClass('test1.csv')
like image 2
Booboo Avatar answered Oct 23 '22 16:10

Booboo