KubiK888
KubiK888

Reputation: 4723

*args and other parameters in decorated function in python

I have the following script that works fine. The decorator on_or_off takes the value from on_switch from the function and execute if its value is True.

import pandas as pd
import ctypes
import sec4_analysis as analysis

# Main class
######################################################################
class Analysis_ProjectX_Demographic(analysis.Analysis_ProjectX):
    def __init__(self):
        super()
        super().__init__()

    def demographic_analytic_steps(self):
        self.import_parent_ref_data()
        self.import_master_data()
        self.recategorize_var(on_switch=True)
        self.result_in_plaintext(on_switch=True)
        self.result_in_csv(on_switch=True)

    # Decorators
    def on_or_off(func):
        def wrapper(self, on_switch, *args):
            if on_switch:
                func(self, on_switch, *args)
        return wrapper

    # Core class functions
    @on_or_off
    def recategorize_var(self, on_switch=False):
        self.df_master_filtered = self.recat_binary(self.df_master_filtered, 'INDEX_RURAL', 'INDEX_RURAL_CAT', 0, 'URBAN', 1, 'RURAL')
        self.df_master_filtered = self.recat_age(self.df_master_filtered, 'INDEX_AGE', 'INDEX_AGE_CAT')

    @on_or_off
    def result_in_plaintext(self, on_switch=False):
        df_dict = {
            'TxGroup':self.df_master_filtered, 
            }
        for df_key, df in df_dict.items():
            print ('Dataset name: {}'.format(df_key))
            print ('Unique patients, n: {}'.format(df['PHN_ENC'].nunique()))
            self.descriptive_num_var_results(df_key, df, 'INDEX_AGE')
            self.descriptive_cat_var_results(df_key, df, 'INDEX_AGE_CAT')
            self.descriptive_cat_var_results(df_key, df, 'INDEX_RURAL_CAT')
            self.descriptive_cat_var_results(df_key, df, 'INDEX_SEX')

    @on_or_off
    def result_in_csv(self, on_switch=False):
        pass

    # Helper functions
    def recat_binary(self, df, old_var, new_var, old_val1, new_val1, old_val2, new_val2):
        df.loc[df[old_var] == old_val1, new_var] = new_val1 
        df.loc[df[old_var] == old_val2, new_var] = new_val2 
        return df

    def recat_age(self, df, old_var, new_var):
        df.loc[(df[old_var]>=19.00)&(df[old_var]<25.00), new_var] = '19-24'
        df.loc[(df[old_var]>=25.00)&(df[old_var]<30.00), new_var] = '25-29'
        df.loc[(df[old_var]>=30.00)&(df[old_var]<35.00), new_var] = '30-34'
        df.loc[(df[old_var]>=35.00)&(df[old_var]<40.00), new_var] = '35-39'
        df.loc[(df[old_var]>=40.00)&(df[old_var]<45.00), new_var] = '40-44'
        df.loc[(df[old_var]>=45.00)&(df[old_var]<50.00), new_var] = '45-49'
        df.loc[(df[old_var]>=50.00)&(df[old_var]<55.00), new_var] = '50-54'
        df.loc[(df[old_var]>=55.00)&(df[old_var]<60.00), new_var] = '55-59'
        df.loc[(df[old_var]>=60.00)&(df[old_var]<65.00), new_var] = '60-64'
        df.loc[(df[old_var]>=65.00)&(df[old_var]<300.00), new_var] = '65/above'
        return df

x = Analysis_ProjectX_Demographic()
x.demographic_analytic_steps()

However, the decorated functions are supposed to be free to have any number of parameters besides the on_switch. When I introduce more parameter some_text in the result_in_plaintext.().

import pandas as pd
import ctypes
import sec4_analysis as analysis

# Main class
######################################################################
class Analysis_ProjectX_Demographic(analysis.Analysis_ProjectX):
    def __init__(self):
        super()
        super().__init__()

    def demographic_analytic_steps(self):
        self.import_parent_ref_data()
        self.import_master_data()
        self.recategorize_var(on_switch=True)
        self.result_in_plaintext(some_text='This is done', on_switch=True)
        self.result_in_csv(on_switch=True)

    # Decorators
    def on_or_off(func):
        def wrapper(self, on_switch, *args):
            if on_switch:
                func(self, on_switch, *args)
        return wrapper

    # Core class functions
    @on_or_off
    def recategorize_var(self, on_switch=False):
        self.df_master_filtered = self.recat_binary(self.df_master_filtered, 'INDEX_RURAL', 'INDEX_RURAL_CAT', 0, 'URBAN', 1, 'RURAL')
        self.df_master_filtered = self.recat_age(self.df_master_filtered, 'INDEX_AGE', 'INDEX_AGE_CAT')

    @on_or_off
    def result_in_plaintext(self, some_text, on_switch=False):
        df_dict = {
            'TxGroup':self.df_master_filtered, 
            }
        for df_key, df in df_dict.items():
            print ('Dataset name: {}'.format(df_key))
            print ('Unique patients, n: {}'.format(df['PHN_ENC'].nunique()))
            self.descriptive_num_var_results(df_key, df, 'INDEX_AGE')
            self.descriptive_cat_var_results(df_key, df, 'INDEX_AGE_CAT')
            self.descriptive_cat_var_results(df_key, df, 'INDEX_RURAL_CAT')
            self.descriptive_cat_var_results(df_key, df, 'INDEX_SEX')

        print(some_text)

    @on_or_off
    def result_in_csv(self, on_switch=False):
        pass

    # Helper functions
    def recat_binary(self, df, old_var, new_var, old_val1, new_val1, old_val2, new_val2):
        df.loc[df[old_var] == old_val1, new_var] = new_val1 
        df.loc[df[old_var] == old_val2, new_var] = new_val2 
        return df

    def recat_age(self, df, old_var, new_var):
        df.loc[(df[old_var]>=19.00)&(df[old_var]<25.00), new_var] = '19-24'
        df.loc[(df[old_var]>=25.00)&(df[old_var]<30.00), new_var] = '25-29'
        df.loc[(df[old_var]>=30.00)&(df[old_var]<35.00), new_var] = '30-34'
        df.loc[(df[old_var]>=35.00)&(df[old_var]<40.00), new_var] = '35-39'
        df.loc[(df[old_var]>=40.00)&(df[old_var]<45.00), new_var] = '40-44'
        df.loc[(df[old_var]>=45.00)&(df[old_var]<50.00), new_var] = '45-49'
        df.loc[(df[old_var]>=50.00)&(df[old_var]<55.00), new_var] = '50-54'
        df.loc[(df[old_var]>=55.00)&(df[old_var]<60.00), new_var] = '55-59'
        df.loc[(df[old_var]>=60.00)&(df[old_var]<65.00), new_var] = '60-64'
        df.loc[(df[old_var]>=65.00)&(df[old_var]<300.00), new_var] = '65/above'
        return df

x = Analysis_ProjectX_Demographic()
x.demographic_analytic_steps()

It gave this error:

line 16, in demographic_analytic_steps
self.result_in_plaintext(some_text='This is done', on_switch=True)
TypeError: wrapper() got an unexpected keyword argument 'some_text'

Upvotes: 0

Views: 31

Answers (2)

Blckknght
Blckknght

Reputation: 104712

The *args parameter you're using in your wrapper function only supports additional arguments passed in as positional arguments. It doesn't support arbitrary keyword arguments. If you want to match keyword arguments, you need to use **kwargs (either instead of or in addition to *args). Otherwise you'd need to change how you call the function, to pass some_text as a positional argument instead of a keyword argument.

Note that the order of the positional arguments matters! You'll need to put the *args later in the argument list than other positional arguments, which means that on_switch needs to be the first argument, not the second one if you are passing them positionally. This is one reason to favor keyword arguments, as their order does not matter. You can even make keyword-only arguments, by putting them after the *args parameter in the function declaration (or after a * by itself, if you don't need *args at all). If you want the most general support possible, I'd suggest:

# Decorators
def on_or_off(func):
    def wrapper(self, *args, on_switch, **kwargs):  # on_switch is keyword only now!
        if on_switch:
            func(self, *args, on_switch=on_switch, **kwargs) # note you may want to return here
    return wrapper

Upvotes: 1

Abhijeet Lokhande
Abhijeet Lokhande

Reputation: 9

class MyClass:
    def on_or_off(func):
            def wrapper(self,*args, **kwargs):
                if kwargs['on_switch']:
                    func(**kwargs)
            return wrapper
    @on_or_off
    def test(on_switch = False,some_text="This is done"):
          print(f' "Test" Function executed with on_switch = {on_switch} and some_text {some_text}')

obj = MyClass()
obj.test(on_switch = True, some_text="Abhijeet")

Upvotes: 1

Related Questions