Reputation: 4723
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
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
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