Replacing multiple `if-else` with single `if-else` in a class [duplicate] - python

This question already has answers here:
How to call Python functions dynamically [duplicate]
(11 answers)
Calling a function of a module by using its name (a string)
(18 answers)
Closed 3 years ago.
So this is more of a trivial problem of writing a clean Python3 code. Let's say I have a class function which can create many function types based on the user input.
import numpy as np
class functions(object):
def __init__(self, typeOfFunction, amplitude, omega, start = None, stop = None,
pulsewidth = None):
self.typeOfFunction = typeOfFunction
self.amplitude = amplitude
self.omega = omega
self.period = 2 * np.pi/omega
self.start = start
self.stop = stop
self.pulsewidth = pulsewidth
def sine_function(self, t):
func = self.amplitude * np.sin(self.omega*t)
return func
def cosine_function(self, t):
func = self.amplitude * np.cos(self.omega*t)
return func
def unit_step_function(self, t):
func = self.amplitude * np.where(t > self.start, 1, 0)
return func
Now my question is let us say we want to write 3 other functions:
Differentiation
Integration
Evaluation at a given time.
Now my problem is that in each of these function I have to put conditions such as these:
def evaluate_function(self, time):
if(self.typeOfFunction == 'sine'):
funcValue = self.sine_function(time)
elif(self.typeOfFunction == 'cosine'):
funcValue = self.cosine_function(time)
elif(self.typeOfFunction == 'unit_step_function'):
funcValue = self.unit_step_function(time)
I want to do it only once in the __init__ method and at subsequent steps just pass the arguments instead of writing if-else:
def __init__(self, typeOfFunction, amplitude, omega, start = None, stop = None,
pulsewidth = None):
self.typeOfFunction = typeOfFunction
self.amplitude = amplitude
self.omega = omega
self.period = 2 * np.pi/omega
self.start = start
self.stop = stop
self.pulsewidth = pulsewidth
#DO SOMETHING THAT MAKES THE TYPE OF FUNCTION EMBEDDED
IN THE CLASS IN A CLASS VARIABLE
And then:
def evaluate_function(self, time):
value = self.doSomething(time)
return value
How can this be done? If duplicate question exists please inform me in the comments.

You can use the method getattr(CLASS_OBJECT, METHOD_ORVARIABLE_NAME) like this:
method = getattr(self, self.typeOfFunction)
and then call method:
method()
or for short:
getattr(self, self.typeOfFunction)()
Also you can check if the attribute you are getting exist or not:
if hasattr(self, self.typeOfFunction):
getattr(self, self.typeOfFunction)()

I think you want a mapping with a dict.
Something like this:
class functions(object):
def evaluate_function(self, which, time):
mapping = {'sine': self.sine_function,
'cosine': self.cosine_function,
# ...more functions here...
}
return mapping[which](time)
# rest of class here...

Related

Explain how probSecond.calls equal to zero

def MainCount(f):
def progFirst(*args,**kwargs):
progFirst.calls+=1
return f(*args,**kwargs)
progFirst.calls=0
return progFirst
#MainCount
def progSecond(i):
return i+1
#MainCount
def Count(i=0,j=1):
return i*j+1
print(progSecond.calls)
for n in range(5):
progSecond(n)
Count(j=0,i=1)
print(Count.calls)
Output :0
1
As per my understanding MainCount(probSecond) but I am not understant then how probSecond.calls equal to zero same in Count.calls also
As You Can See in MainCount function probFirst.Calls is attribute of function .When MainCount(probSecond) Now probSecond.calls is also attribute of MainCount function.
# A Python example to demonstrate that
# decorators can be useful attach data
# A decorator function to attach
# data to func
def attach_data(func):
func.data = 3
return func
#attach_data
def add (x, y):
return x + y
# Driver code
# This call is equivalent to attach_data()
# with add() as parameter
print(add(2, 3))
print(add.data)

Python possible to short circuit function call

I am building a function to construct objects with set attributes (similar to a namedtuple); however, the output length must be variable.
I would like to build a function that allows the user to append additional attributes through a function call. Importantly, I would like to find a way to 'short-circuit' parameters and am unsure if Python is powerful enough to do this.
To explain take this trivial example:
def foo():
print("foo")
return False
def bar():
print("bar")
return True
if foo() and bar():
pass
Foo's function call returns False, and Bar short-circuits. The output console will only print foo, and bar is never executed.
Is there such a way to mimic this behavior with inspection or reflection in respect to function calls. Here is an example with my implementation is shown below:
from inspect import stack
cache = {}
def fooFormat(**kwargs):
caller = stack()[1][3]
if caller not in cache:
class fooOut(object):
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
def optional(self, opt, **kwargs):
if (opt):
self.__dict__.update(kwargs)
return self
def __str__(self):
return caller + str(self.__dict__)
cache[caller] = iadsOut
return cache[caller](**kwargs)
def stdev(nums, avg = None):
print("\tStdev call")
if avg is None:
avg = sum(nums) / len(nums)
residuals = sum((i - avg)**2 for i in nums)
return residuals**.5
def stats(nums, verbose=False):
if verbose:
print("Stats call with verbose")
else:
print("Stats call without verbose")
total = sum(nums)
N = len(nums)
avg = total / N
return fooFormat(
avg = avg,
lowerB = min(nums),
upperB = max(nums)).optional(verbose,
stdev = stdev(nums, avg))
In the function 'stats', the return fooFormat should of course yield avg, lowerB, and upperB; additionally, it should yield std if verbose is set to True. Moreover, the function 'stdev' should NOT be called if verbose is set to False.
stats([1,2,3,4], False)
stats([1,2,3,4], True)
Of course, a way around this is:
if verbose:
return fooFormat(
avg = avg,
lowerB = min(nums),
upperB = max(nums),
stdev = stdev(nums, avg))
else:
return fooFormat(
avg = avg,
lowerB = min(nums),
upperB = max(nums))
However, I am hoping there to implement this behavior without a branch.
This doesn't quite answer the shortcutting point, but this is a more efficient way of writing it:
out_dic = { # these items will always be calculated
'avg': avg,
'lowerB':max(nums),
'upperB':min(nums)
}
if verbose: # this is calculated only if verbose
out_dic['stdev'] = stdev(nums,avg)
return fooFormat(**out_dic)
In other words you can expand a dictionary to the kwargs, and add to the dictionary dynamically.

How to use two helper functions in main script from another script

TypeError: _slow_trap_ramp() takes 1 positional argument but 2 were given
def demag_chip(self):
coil_probe_constant = float(514.5)
field_sweep = [50 * i * (-1)**(i + 1) for i in range(20, 0, -1)] #print as list
for j in field_sweep:
ramp = self._slow_trap_ramp(j)
def _set_trap_ramp(self):
set_trap_ramp = InstrumentsClass.KeysightB2962A.set_trap_ramp
return set_trap_ramp
def _slow_trap_ramp(self):
slow_trap_ramp = ExperimentsSubClasses.FraunhoferAveraging.slow_trap_ramp
return slow_trap_ramp
The error is straightforward.
ramp = self._slow_trap_ramp(j)
You are calling this method with an argument j, but the method doesn't take an argument (other than self, which is used to pass the object).
Re-define your method to accept an argument if you want to pass it one:
def _slow_trap_ramp(self, j):
It looks like your code extract contains methods of some class, whose full definition is not shown, and you are calling one method from another method (self._slow_trap_ramp(j)). When you call a method, Python automatically passes self before any other arguments. So you need to change def _slow_trap_ramp(self) to def _slow_trap_ramp(self, j).
Update in response to comment
To really help, we would need to see more of the class you are writing, and also some info on the other objects you are calling. But I am going to go out on a limb and guess that your code looks something like this:
InstrumentsClass.py
class KeysightB2962A
def __init__(self):
...
def set_trap_ramp(self):
...
ExperimentsSubClasses.py
class FraunhoferAveraging
def __init__(self):
...
def slow_trap_ramp(self, j):
...
Current version of main.py
import InstrumentsClass, ExperimentsSubClasses
class MyClass
def __init__(self)
...
def demag_chip(self):
coil_probe_constant = float(514.5)
field_sweep = [50 * i * (-1)**(i + 1) for i in range(20, 0, -1)] #print as list
for j in field_sweep:
ramp = self._slow_trap_ramp(j)
def _set_trap_ramp(self):
set_trap_ramp = InstrumentsClass.KeysightB2962A.set_trap_ramp
return set_trap_ramp
def _slow_trap_ramp(self):
slow_trap_ramp = ExperimentsSubClasses.FraunhoferAveraging.slow_trap_ramp
return slow_trap_ramp
if __name__ == "__main__":
my_obj = MyClass()
my_obj.demag_chip()
If this is the case, then these are the main problems:
Python passes self and j to MyClass._slow_trap_ramp, but you've only defined it to accept self (noted above),
you are using class methods from KeysightB2962A and FraunhoferAveraging directly instead of instantiating the class and using the instance's methods, and
you are returning references to the methods instead of calling the methods.
You can fix all of these by changing the code to look like this (see embedded comments):
New version of main.py
import InstrumentsClass, ExperimentsSubClasses
class MyClass
def __init__(self)
# create instances of the relevant classes (note parentheses at end)
self.keysight = InstrumentsClass.KeysightB2962A()
self.fraun_averaging = ExperimentsSubClasses.FraunhoferAveraging()
def demag_chip(self):
coil_probe_constant = float(514.5)
field_sweep = [50 * i * (-1)**(i + 1) for i in range(20, 0, -1)] #print as list
for j in field_sweep:
ramp = self._slow_trap_ramp(j)
def _set_trap_ramp(self):
# call instance method (note parentheses at end)
return self.keysight.set_trap_ramp()
def _slow_trap_ramp(self, j): # accept both self and j
# call instance method (note parentheses at end)
return self.fraun_averaging.slow_trap_ramp(j)
if __name__ == "__main__":
my_obj = MyClass()
my_obj.demag_chip()

Python - Can't implement Scipy fitting script

I am trying to implement the Scipy script from section "Simplifying the syntax" here: http://scipy-cookbook.readthedocs.io/items/FittingData.html
My code is quite long, so I'll post only the parts that seem to be the problem.
I get the following error message: TypeError: unsupported operand type(s) for *: 'int' and 'Parameter', which I understand why it happens: it's the product in this part: return self.amplitude() * np.exp(-1*self.decay_const()*x)
class Plot():
def __init__(self,slice_and_echo,first_plot,right_frame):
self.slice_and_echo = slice_and_echo
self.first_plot = first_plot
self.right_frame = right_frame
self.amplitude = Parameter(1)
self.decay_const = Parameter(1)
def function(self,x):
print(self.amplitude)
print(self.amplitude())
return self.amplitude() * np.exp(-1*self.decay_const()*x)
def create_plot(self):
plot_figure = Figure(figsize=(10,10), dpi=100)
self.the_plot = plot_figure.add_subplot(111)
self.the_plot.plot(self.echoes,self.average,'ro')
print(self.amplitude())
self.fit_parameters = self.fit(self.function,[self.amplitude,self.decay_const],self.average)
print(self.fit_parameters)
def fit(self,function, parameters, y, x=None):
def f(params):
i = 0
for p in parameters:
p.set(params[i])
i += 1
return y - function(x)
if x is None: x = np.arange(y.shape[0])
p = [param for param in parameters]
return optimize.leastsq(f, p)
and the Parameter() class is the same as in the link:
class Parameter:
def __init__(self, value):
self.value = value
def set(self, value):
self.value = value
def __call__(self):
return self.value
The issue seems to be that, when I call self.amplitude() inside of the create_plot(self): method, the value it returns is an integer (which is what I want!). But that doesn't happen when I call it inside of the function(self,x) method; when I print it inside this method I get: <__main__.Parameter object at 0x1162845c0> instead of the integer 1.
Why would it return different values when called from different methods in the same class? What am I missing here?
Thank you!
You got a typo in list comprehension. Your code states:
p = [param for param in parameters]
and the example code states:
p = [param() for param in parameters]
Note that in your case you are generating a list of objects of type Parameter instead of a list of numbers.
By the way, check out module called lmfit - it simplifies fitting routines by great deal.

Returning two values from pandas.rolling_apply

I am using pandas.rolling_apply to fit data to a distribution and get a value from it, but I need it also report a rolling goodness of fit (specifically, p-value). Currently I'm doing it like this:
def func(sample):
fit = genextreme.fit(sample)
return genextreme.isf(0.9, *fit)
def p_value(sample):
fit = genextreme.fit(sample)
return kstest(sample, 'genextreme', fit)[1]
values = pd.rolling_apply(data, 30, func)
p_values = pd.rolling_apply(data, 30, p_value)
results = pd.DataFrame({'values': values, 'p_value': p_values})
The problem is that I have a lot of data, and the fit function is expensive, so I don't want to call it twice for every sample. What I'd rather do is something like this:
def func(sample):
fit = genextreme.fit(sample)
value = genextreme.isf(0.9, *fit)
p_value = kstest(sample, 'genextreme', fit)[1]
return {'value': value, 'p_value': p_value}
results = pd.rolling_apply(data, 30, func)
Where results is a DataFrame with two columns. If I try to run this, I get an exception:
TypeError: a float is required. Is it possible to achieve this, and if so, how?
I had a similar problem and solved it by using a member function of a separate helper class during apply. That member function does as required return a single value but I store the other calc results as members of the class and can use it afterwards.
Simple Example:
class CountCalls:
def __init__(self):
self.counter = 0
def your_function(self, window):
retval = f(window)
self.counter = self.counter + 1
TestCounter = CountCalls()
pandas.Series.rolling(your_seriesOrDataframeColumn, window = your_window_size).apply(TestCounter.your_function)
print TestCounter.counter
Assume your function f would return a tuple of two values v1,v2. Then you can return v1 and assign it to column_v1 to your dataframe. The second value v2 you simply accumulate in a Series series_val2 within the helper class. Afterwards you just assing that series as new column to your dataframe.
JML
I had a similar problem before. Here's my solution for it:
from collections import deque
class your_multi_output_function_class:
def __init__(self):
self.deque_2 = deque()
self.deque_3 = deque()
def f1(self, window):
self.k = somefunction(y)
self.deque_2.append(self.k[1])
self.deque_3.append(self.k[2])
return self.k[0]
def f2(self, window):
return self.deque_2.popleft()
def f3(self, window):
return self.deque_3.popleft()
func = your_multi_output_function_class()
output = your_pandas_object.rolling(window=10).agg(
{'a':func.f1,'b':func.f2,'c':func.f3}
)
I used and loved #yi-yu's answer so I made it generic:
from collections import deque
from functools import partial
def make_class(func, dim_output):
class your_multi_output_function_class:
def __init__(self, func, dim_output):
assert dim_output >= 2
self.func = func
self.deques = {i: deque() for i in range(1, dim_output)}
def f0(self, *args, **kwargs):
k = self.func(*args, **kwargs)
for queue in sorted(self.deques):
self.deques[queue].append(k[queue])
return k[0]
def accessor(self, index, *args, **kwargs):
return self.deques[index].popleft()
klass = your_multi_output_function_class(func, dim_output)
for i in range(1, dim_output):
f = partial(accessor, klass, i)
setattr(klass, 'f' + str(i), f)
return klass
and given a function f of a pandas Series (windowed but not necessarily) returning, n values, you use it this way:
rolling_func = make_class(f, n)
# dict to map the function's outputs to new columns. Eg:
agger = {'output_' + str(i): getattr(rolling_func, 'f' + str(i)) for i in range(n)}
windowed_series.agg(agger)
I also had the same issue. I solved it by generating a global data frame and feeding it from the rolling function. In the following example script, I generate a random input data. Then, I calculate with a single rolling apply function the min, the max and the mean.
import pandas as pd
import numpy as np
global outputDF
global index
def myFunction(array):
global index
global outputDF
# Some random operation
outputDF['min'][index] = np.nanmin(array)
outputDF['max'][index] = np.nanmax(array)
outputDF['mean'][index] = np.nanmean(array)
index += 1
# Returning a useless variable
return 0
if __name__ == "__main__":
global outputDF
global index
# A random window size
windowSize = 10
# Preparing some random input data
inputDF = pd.DataFrame({ 'randomValue': [np.nan] * 500 })
for i in range(len(inputDF)):
inputDF['randomValue'].values[i] = np.random.rand()
# Pre-Allocate memory
outputDF = pd.DataFrame({ 'min': [np.nan] * len(inputDF),
'max': [np.nan] * len(inputDF),
'mean': [np.nan] * len(inputDF)
})
# Precise the staring index (due to the window size)
d = (windowSize - 1) / 2
index = np.int(np.floor( d ) )
# Do the rolling apply here
inputDF['randomValue'].rolling(window=windowSize,center=True).apply(myFunction,args=())
assert index + np.int(np.ceil(d)) == len(inputDF), 'Length mismatch'
outputDF.set_index = inputDF.index
# Optional : Clean the nulls
outputDF.dropna(inplace=True)
print(outputDF)

Categories