Link function in an __init__ - python

I am doing some coding in python and I've come across little problem :
I defined several version of functions and want to assign one of them in the __init__ function when I create my object. I need those function to have a self argument.
I found a way to do it but I cannot tell if I am using the language correctly.
In my_class.py
class my_class(object):
fun_1 = lambda self,val: self.attr + val
fun_2 = lambda self,val: self.attr - val
def __init__(self, attr, fun_version):
self.attr = attr
my_class.fun = my_class.__dict__['fun_' + str(fun_version)]
In test.py
import my_class
my_instance_1 = my_class.my_class(attr = 2, fun_version = 1)
print(my_instance_1.fun(1))
my_instance_2 = my_class.my_class(attr = 2, fun_version = 2)
print(my_instance_2.fun(1))
The result follows what is expected :
3
1
Is there more elegant way to do it ? I find awkward to define a class method in the __init__ function.

A simple way to achieve what you want is (class and test code in one file for simplicity):
class my_class(object):
fun_1 = lambda self,val: self.attr + val
fun_2 = lambda self,val: self.attr - val
def __init__(self, attr, fun_version):
self.attr = attr
self.fun = getattr(self, 'fun_' + str(fun_version))
my_instance_1 = my_class(attr = 2, fun_version = 1)
print(my_instance_1.fun(1))
my_instance_2 = my_class(attr = 2, fun_version = 2)
print(my_instance_2.fun(1))
#Proof that instances are independent:
print(my_instance_1.fun(1))
Prints:
3
1
3
If the variants of fun don't need instance attributes (are static methods) this can be written this way (it could also be written with lambda in some way similar to first variant but this will look ugly):
class my_class(object):
#staticmethod
def fun_1(val):
return 1 + val
#staticmethod
def fun_2(val):
return 2 + val
def __init__(self, attr, fun_version):
self.attr = attr
self.fun = getattr(my_class, 'fun_' + str(fun_version))
# or (as above):
# self.fun = getattr(self, 'fun_' + str(fun_version))
my_instance_1 = my_class(attr = 2, fun_version = 1)
print(my_instance_1.fun(1))
my_instance_2 = my_class(attr = 2, fun_version = 2)
print(my_instance_2.fun(1))
print(my_instance_1.fun(1))
Prints
2
3
2

Related

how to fix TypeError: class() takes no arguments problem?

I am trying to create a class name RangePrime and it's instance attributes should print out the range of them and finally appear an issue. How to fix them?
class RangePrime:
def range_list(self,a,b):
self.a = a
self.b = b
x = []
if b < a:
x.extend(range(b, a))
x.append(a)
else:
x.extend(range(a, b))
x.append(b)
return x
after i ran this -> range_prime = RangePrime(1, 5) and it start to appear
TypeError: RangePrime() takes no arguments
I should get the following result:
>>> range_prime = RangePrime(1, 5)
>>> range_prime.range_list
[1, 2, 3, 4, 5]
It looks you have mixed using functions without a class definition and at the same time misdefining the functions within the class by lacking an __init__(). I have modified your code a bit to account for the class and its functions, so the intention of your code remains the same. Kindly try:
class RangePrime():
def __init__(self, a,b):
self.a = a
self.b = b
def range_list(self):
a = self.a
b = self.b
x = []
if b < a:
x.extend(range(b, a))
x.append(a)
else:
x.extend(range(a, b))
x.append(b)
return x
Which when running:
range_prime = RangePrime(1, 5)
range_prime.range_list()
Returns:
[1,2,3,4,5]
create object of the class first and then pass those parameter in your class method then it will work
ob=RangePrime()
print(ob.range_list(1,5))

A method inside a class that calls another method

I need help with the below code. I want to use the get_skies, get_high, and get_low method to call the set_skies, set_high, and set_low methods, respectively, and then return the value for init_skies, init_high, and init_low, respectively.
This is what I have got so far:
class WeatherForecast():
def set_skies(self, init_skies):
return init_skies
def set_high(self, init_high):
return init_high
def set_low(self, init_low):
return init_low
def get_skies(self):
self.set_skies()
def get_high(self):
self.set_high()
def get_low(self):
self.set_low()
In python attributes of class are publically accessible.
You don't need to use getter or setters for attributes unless you want to perform some kind of preprocessing or mutation of the attribute
In your case, you can try this,
class WeatherForecast():
def __init__(self, init_skies, init_low, init_high):
self._init_skies = init_skies
self._init_low = init_low
self._init_high = init_high
#property
def skies(self):
return self._init_skies
#property
def high(self):
return self._init_high
#property
def low(self):
return self._init_low
#skies.setter
def skies(self, value):
self._init_skies = value
#high.setter
def high(self, value):
self._init_high = value
#low.setter
def low(self, value):
self._init_low = value
w = WeatherForecast(1, 2, 3)
print(w.skies, w.low, w.high) # --> print the values
# Set the values
w.skies = 10
w.low = 20
w.high = 30
print(w.skies, w.low, w.high) # --> print the updated values

Design pattern for relational and optional parameters?

I am to build a class that accepts a series of inputs via the constructor method, then perform a calculation with calculate() using these parameters. The trick here is that these parameters might be available sometimes and other times might not. There however, is a given equation between the variables, such that the missing ones can be calculated from the equations. Here is an example:
I know that:
a = b * c - d
c = e/f
I am to calculate always a+b+c+d+e+f
Here is what I have so far:
class Calculation:
def __init__(self, **kwargs):
for parameter, value in kwargs.items():
setattr(self, '_'.format(parameter), value)
#property
def a(self):
try:
return self._a
except AttributeError:
return self._b * self._c - self._d
#property
def b(self):
try:
return self._b
except AttributeError:
return (self._a + self._d) / self._c
... // same for all a,b,c,d,e,f
def calculate(self):
return sum(self.a+self.b+self.c+self.d+self.e+self.f)
then use as:
c = Calculation(e=4,f=6,b=7,d=2)
c.calculate()
however, some other time might have other variables like:
c = Calculation(b=5,c=6,d=7,e=3,f=6)
c.calculate()
My question is: What would be a good design pattern to use in my case? So far, it seems a bit redundant to make a #property for all variables. The problem it must solve is to accept any variables (minimum for which calculation is possible) and based on the equation I have, figure out the rest, needed for calculation.
This seems like a good candidate for the getattr function. You can store the keyword arguments directly in the class and use that dictionary to either return a known parameter as attribute or infer an unspecified value "on the fly" based on other formulas that you know of:
class Calculation:
def __init__(self, **kwargs):
self.params = kwargs
self.inferred = {
"a" : lambda: self.b * self.c - self.d,
"c" : lambda: self.e / self.f,
"result": lambda: self.a+self.b+self.c+self.d+self.e+self.f
}
def __getattr__(self, name):
if name in self.params:
return self.params[name]
if name in self.inferred:
value = self.inferred[name]()
self.params[name] = value
return value
r = Calculation(b=1,d=3,e=45,f=9).result
print(r) # 65.0 (c->45/9->5, a->1*5-3->2)
Note that, if you have very complicated calculations for some of the parameters, you can use functions of the class as the implementation of the lambdas in the self.inferred dictionary.
If you're going to use this pattern for many formulas, you might want to centralize the boilerplate code in a base class. This will reduce the work needed for new calculation classes to only having to implement the inferred() function.:
class SmartCalc:
def __init__(self, **kwargs):
self.params = kwargs
def __getattr__(self, name):
if name in self.params:
return self.params[name]
if name in self.inferred():
value = self.inferred()[name]()
self.params[name] = value
return value
class Calculation(SmartCalc):
def inferred(self):
return {
"a" : lambda: self.b * self.c - self.d,
"b" : lambda: (self.a+self.d)/self.c,
"c" : lambda: self.e / self.f,
"d" : lambda: self.c * self.b - self.a,
"e" : lambda: self.f * self.c,
"f" : lambda: self.e / self.c,
"result": lambda: self.a+self.b+self.c+self.d+self.e+self.f
}
With enough content in inferred(), you can even use this approach to obtain any value from a combination of the others:
valueF = Calculation(a=2,b=1,c=5,d=3,e=45,result=65).f
print(valueF) # 9.0
EDIT
If you want to make this even more sophisticated, you can improve getattr to allow specification of dependencies in the inferred() dictionary.
For example:
class SmartCalc:
def __init__(self, **kwargs):
self.params = kwargs
def __getattr__(self, name):
if name in self.params:
return self.params[name]
if name in self.inferred():
calc = self.inferred()[name]
if isinstance(calc,dict):
for names,subCalc in calc.items():
if isinstance(names,str): names = [names]
if all(name in self.params for name in names):
calc = subCalc; break
value = calc()
self.params[name] = value
return value
import math
class BodyMassIndex(SmartCalc):
def inferred(self):
return {
"heightM" : { "heightInches": lambda: self.heightInches * 0.0254,
("bmi","weightKg"): lambda: math.sqrt(self.weightKg/self.bmi),
("bmi","weightLb"): lambda: math.sqrt(self.weightKg/self.bmi)
},
"heightInches" : lambda: self.heightM / 0.0254,
"weightKg" : { "weightLb": lambda: self.weightLb / 2.20462,
("bmi","heightM"): lambda: self.heightM**2*self.bmi,
("bmi","heightInches"): lambda: self.heightM**2*self.bmi
},
"weightLb" : lambda: self.weightKg * 2.20462,
"bmi" : lambda: self.weightKg / (self.heightM**2)
}
bmi = BodyMassIndex(heightM=1.75,weightKg=130).bmi
print(bmi) # 42.44897959183673
height = BodyMassIndex(bmi=42.45,weightKg=130).heightInches
print(height) # 68.8968097135968 (1.75 Meters)
EDIT2
A similar class could be designed to process formulas expressed as text. This would allow a basic form of term solver using a newton-raphson iterative approximation (at least for 1 degree polynomial equations):
class SmartFormula:
def __init__(self, **kwargs):
self.params = kwargs
self.moreToSolve = True
self.precision = 0.000001
self.maxIterations = 10000
def __getattr__(self, name):
self.resolve()
if name in self.params: return self.params[name]
def resolve(self):
while self.moreToSolve:
self.moreToSolve = False
for formula in self.formulas():
param = formula.split("=",1)[0].strip()
if param in self.params: continue
if "?" in formula:
self.useNewtonRaphson(param)
continue
try:
exec(formula,globals(),self.params)
self.moreToSolve = True
except: pass
def useNewtonRaphson(self,name):
for formula in self.formulas():
source,calc = [s.strip() for s in formula.split("=",1)]
if name not in calc: continue
if source not in self.params: continue
simDict = self.params.copy()
target = self.params[source]
value = target
try:
for _ in range(self.maxIterations):
simDict[name] = value
exec(formula,globals(),simDict)
result = simDict[source]
resultDelta = target-result
value += value*resultDelta/result/2
if abs(resultDelta) < self.precision/2 :
self.params[name] = round(simDict[name]/self.precision)*self.precision
self.moreToSolve = True
return
except: continue
With this approach the BodyMassIndex calculator would be easier to read:
import math
class BodyMassIndex(SmartFormula):
def formulas(self):
return [
"heightM = heightInches * 0.0254",
"heightM = ?", # use Newton-Raphson solver.
"heightInches = ?",
"weightKg = weightLb / 2.20462",
"weightKg = heightM**2*bmi",
"weightLb = ?",
"bmi = weightKg / (heightM**2)"
]
This lets you obtain/use terms for which the calculation formula is not explicitly stated in the list (e.g. heightInches computed from heightM which is computed from bmi and weightKg):
height = BodyMassIndex(bmi=42.45,weightKg=130).heightInches
print(height) # 68.8968097135968 (1.75 Meters)
Note: The formulas are expressed as text and executed using eval() which may be much slower than the other solution. Also, the Newton-Raphson algorithm is OK for linear equations but has its limitations for curves that have a mix of positive and negative slopes. For example, I had to include the weightKg = heightM**2*bmi formula because obtaining weightKg based on bmi = weightKg/(heightM**2) needs to solve a y = 1/x^2 equation which Newton-Raphson can't seem to handle.
Here's an example using your original problem:
class OP(SmartFormula):
def formulas(self):
return [
"a = b * c - d",
"b = ?",
"c = e/f",
"d = ?",
"e = ?",
"f = ?",
"result = a+b+c+d+e+f"
]
r = OP(b=1,d=3,e=45,f=9).result
print(r) # 65.0
f = OP(a=2,c=5,d=3,e=45,result=65).f
print(f) # 9.0
class ABCD(SmartFormula):
def formulas(self) : return ["a=b+c*d","b=?","c=?","d=?"]
#property
def someProperty(self): return "Found it!"
abcd = ABCD(a=5,b=2,c=3)
print(abcd.d) # 1.0
print(abcd.someProperty) # Found it!
print(abcd.moreToSolve) # False
Just precompute the missing values in __init__ (and since you know what the 5 values are, be explicit rather than trying to compress the code using kwargs):
# Note: Make all 6 keyword-only arguments
def __init__(self, *, a=None, b=None, c=None, d=None, e=None, f=None):
if a is None:
a = b * c - d
if c is None:
c = e / f
self.sum = a + b + c + d + e + f
def calculate(self):
return self.sum
[New Answer to complement previous one]
I felt my answer was getting too big so I'm adding this improved solution in a separate one.
This is a basic algebra solver to for simple equations that will output an assignment statement for a different term of the input equation:
For example:
solveFor("d","a=b+c/d") # --> 'd=c/(a-b)'
With this function, you can further improve the SmartFormula class by attempting to use algebra before reverting to Newton-Raphson. This will provide more reliable results when the equation is simple enough for the solveFor() function.
The solveFor() function can solve the equation for any term that appears only once in the formula. It will "understand" the calculation as long as the components to solve are only related with basic operations (+, -, *, /, **). Any group in parentheses that does not contain the target term will be processed "as is" without being further interpreted. This allows you to place complex functions/operators in parentheses so that other terms can be solved even in the presence of these special calculations.
import re
from itertools import accumulate
def findGroups(expression):
levels = list(accumulate(int(c=="(")-int(c==")") for c in expression))
groups = "".join([c,"\n"][lv==0] for c,lv in zip(expression,levels)).split("\n")
groups = [ g+")" for g in groups if g ]
return sorted(groups,key=len,reverse=True)
functionMap = [("sin","asin"),("cos","acos"),("tan","atan"),("log10","10**"),("exp","log")]
functionMap += [ (b,a) for a,b in functionMap ]
def solveFor(term,equation):
equation = equation.replace(" ","").replace("**","†")
termIn = re.compile(f"(^|\\W){term}($|\\W)")
if len(termIn.findall(equation)) != 1: return None
left,right = equation.split("=",1)
if termIn.search(right): left,right = right,left
groups = { f"#{i}#":group for i,group in enumerate(findGroups(left)) }
for gid,group in groups.items(): left = left.replace(group,gid)
termGroup = next((gid for gid,group in groups.items() if termIn.search(group)),"##" )
def moveTerms(leftSide,rightSide,oper,invOper):
keepLeft = None
for i,x in enumerate(leftSide.split(oper)):
if termGroup in x or termIn.search(x):
keepLeft = x; continue
x = x or "0"
if any(op in x for op in "+-*/"): x = "("+x+")"
rightSide = invOper[i>0].replace("{r}",rightSide).replace("{x}",x)
return keepLeft, rightSide
def moveFunction(leftSide,rightSide,func,invFunc):
fn = leftSide.split("#",1)[0]
if fn.split(".")[-1] == func:
return leftSide[len(fn):],fn.replace(func,invFunc)
return leftSide,rightSide
left,right = moveTerms(left,right,"+",["{r}-{x}"]*2)
left,right = moveTerms(left,right,"-",["{x}-{r}","{r}+{x}"])
left,right = moveTerms(left,right,"*",["({r})/{x}"]*2)
left,right = moveTerms(left,right,"/",["{x}/({r})","({r})*{x}"])
left,right = moveTerms(left,right,"†",["log({r})/log({x})","({r})†(1/{x})"])
for func,invFunc in functionMap:
left,right = moveFunction(left,right,func,f"{invFunc}({right})")
for sqrFunc in ["math.sqrt","sqrt"]:
left,right = moveFunction(left,right,sqrFunc,f"({right})**2")
for gid,group in groups.items(): right = right.replace(gid,group)
if left == termGroup:
subEquation = groups[termGroup][1:-1]+"="+right
return solveFor(term,subEquation)
if left != term: return None
solution = f"{left}={right}".replace("†","**")
# expression clen-up
solution = re.sub(r"(?<!\w)(0\-)","-",solution)
solution = re.sub(r"1/\(1/(\w)\)",r"\g<1>",solution)
solution = re.sub(r"\(\(([^\(]*)\)\)",r"(\g<1>)",solution)
solution = re.sub(r"(?<!\w)\((\w*)\)",r"\g<1>",solution)
return solution
Example Uses:
solveFor("x","y=(a+b)*x-(math.sin(1.5)/322)") # 'x=(y+(math.sin(1.5)/322))/(a+b)'
solveFor("a","q=(a**2+b**2)*(c-d)**2") # 'a=(q/(c-d)**2-b**2)**(1/2)'
solveFor("a","c=(a**2+b**2)**(1/2)") # 'a=(c**2-b**2)**(1/2)'
solveFor("a","x=((a+b)*c-d)*(23+y)") # 'a=(x/(23+y)+d)/c-b'
sa = solveFor("a","y=-sin((x)-sqrt(a))") # 'a=(x-asin(-y))**2'
sx = solveFor("x",sa) # 'x=a**(1/2)+asin(-y)'
sy = solveFor("y",sx) # 'y=-sin(x-a**(1/2))'
Note that you can probably find much better algebra 'solvers' out there, this is just a simple/naive solution.
Here is an improved version of the SmartFormula class that uses solveFor() to attempt an algebra solution before reverting to Newton-Raphson approximations:
class SmartFormula:
def __init__(self, **kwargs):
self.params = kwargs
self.precision = 0.000001
self.maxIterations = 10000
self._formulas = [(f.split("=",1)[0].strip(),f) for f in self.formulas()]
terms = set(term for _,f in self._formulas for term in re.findall(r"\w+\(?",f) )
terms = [ term for term in terms if "(" not in term and not term.isdigit() ]
self._formulas += [ (term,f"{term}=solve('{term}')") for term in terms]
self(**kwargs)
def __getattr__(self, name):
if name in self.params: return self.params[name]
def __call__(self, **kwargs):
self.params = kwargs
self.moreToSolve = True
self.params["solve"] = lambda n: self.autoSolve(n)
self.resolve()
return self.params.get(self._formulas[0][0],None)
def resolve(self):
while self.moreToSolve:
self.moreToSolve = False
for param,formula in self._formulas:
if self.params.get(param,None) is not None: continue
try:
exec(formula,globals(),self.params)
if self.params.get(param,None) is not None:
self.moreToSolve = True
except: pass
def autoSolve(self, name):
for resolver in [self.algebra, self.newtonRaphson]:
for source,formula in self._formulas:
if self.params.get(source,None) is None:
continue
if not re.search(f"(^|\\W){name}($|\\W)",formula):
continue
resolver(name,source,formula)
if self.params.get(name,None) is not None:
return self.params[name]
def algebra(self, name, source, formula):
try: exec(solveFor(name,formula),globals(),self.params)
except: pass
def newtonRaphson(self, name, source,formula):
simDict = self.params.copy()
target = self.params[source]
value = target
for _ in range(self.maxIterations):
simDict[name] = value
try: exec(formula,globals(),simDict)
except: break
result = simDict[source]
resultDelta = target-result
if abs(resultDelta) < self.precision :
self.params[name] = round(value/self.precision/2)*self.precision*2
return
value += value*resultDelta/result/2
This allowed the example class (BodyMassIndex) to avoid specification of the "weightKg = heightM**2*bmi" calculation because the algebra solver can figure it out. The improved class also eliminates the need to indicate auto-solving term names ("term = ?").
import math
class BodyMassIndex(SmartFormula):
def formulas(self):
return [
"bmi = weightKg / (heightM**2)",
"heightM = heightInches * 0.0254",
"weightKg = weightLb / 2.20462"
]
bmi = BodyMassIndex()
print("bmi",bmi(heightM=1.75,weightKg=130)) # 42.44897959183673
print("weight",bmi.weightLb) # 286.6006 (130 Kg)
bmi(bmi=42.45,weightKg=130)
print("height",bmi.heightInches) # 68.8968097135968 (1.75 Meters)
For the original question, this is simple as can be:
class OP(SmartFormula):
def formulas(self):
return [
"result = a+b+c+d+e+f",
"a = b * c - d",
"c = e/f"
]
r = OP(b=1,d=3,e=45,f=9).result
print(r) # 65.0
f = OP(a=2,c=5,d=3,e=45,result=65).f
print(f) # 9.0
Newton-Raphson was not used in any of these calculations because the algebra solves them in priority before trying the approximations

Checking if contained object has changed

I am working on creating a module with a class that acts as a container for a list of another created class. Is there a way for the container class to be able to tell if any of the objects it contains has changed?
Here is an example:
class Part:
def __init__(self, size):
self.part_size = size
class Assembly:
def __init__(self, *parts):
self.parts = list(parts) # `parts` are all Part() objects
self.update()
def update(self):
self.assy_size = 0
for each in self.parts:
self.assy_size += each.part_size
def __getitem__(self, key):
return self.parts[key]
This is what I get if I try to change any of the Part properties in the Assembly:
>>>x = Part(1)
>>>y = Part(1)
>>>z = Part(1)
>>>u = Assembly(x, y, z)
>>>u.assy_size
3
>>>u[0].part_size = 4
>>>u.assy_size
3
I know that I can create additional methods that will call the update method if I replace, delete, or add Part objects to the Assembly, but is there any way to have the Assembly notified if any of the contained Part properties have changed?
The answer is in your question. Use a property.
class Part:
_size = 0
assembly = None
#property
def part_size(self):
return self._size
#part_size.setter
def part_size(self, value):
self._size = value
if self.assembly: # only notify if an Assembly is set
self.assembly.update()
def set_assembly(self, assembly):
self.assembly = assembly
def __init__(self, size):
self.part_size = size
class Assembly:
def __init__(self, *parts):
self.parts = list(parts) # `parts` are all Part() objects
for part in self.parts:
part.set_assembly(self) # reference to self needed to notify changes
self.update()
def update(self):
self.assy_size = 0
for each in self.parts:
self.assy_size += each.part_size
In this version of Assembly the constructor sets a reference on the Part to itself. This way it can update the assembly when the part_size changes. Use it as the example in your question.
>>>x = Part(1)
>>>y = Part(1)
>>>z = Part(1)
>>>u = Assembly(x, y, z)
>>>u.assy_size
3
>>>u[0].part_size = 4
>>>u.assy_size
6
If update isn't an expensive operation (in your example it isn't, but maybe in reality you have thousands of parts), you could calculate the size ad-hoc using a property:
class Assembly:
def __init__(self, *parts):
self.parts = list(parts)
#property
def assy_size(self):
result = 0
for each in self.parts:
result += each.part_size
return result
which can be accessed the same way: assembly.assy_size.
The calculation can also be simplified:
#property
def assy_size(self):
return sum(part.part_size for part in self.parts)

Function decorator that logs the value of specified function arguments in an accessible python object

I am trying to create a function decorator that logs the value specified function arguments in an accessible python object. I have already working code but I am missing a piece to finish this up.
First, I have the object log where I will save stuff correctly set up:
class Borg:
_shared_state = {}
def __init__(self):
self.__dict__ = self._shared_state
class Log(Borg):
def __init__(self):
Borg.__init__(self)
if not hasattr(self, 'tape'):
self.tape = []
def add(self, this):
self.tape.append(this)
def __str__(self):
return '\n'.join([str(line) for line in self.tape])
Then I have a generic call object and the decorator implementation (with missing code):
import inspect
import functools
class Call:
def __init__(self, name, **saved_arguments):
self.name = name
self.saved_arguments = saved_arguments
def __str__(self):
return f'Call(name={self.name}, saved_arguments={self.saved_arguments})'
def record(func, save_args_names=None):
if save_args_names is None:
save_args_names = {}
name = func.__name__
args = inspect.getfullargspec(func).args
if save_args_names and not set(save_args_names).issubset(set(args)):
raise ValueError(f'Arguments not present in function: {set(save_args_names) - set(args)}')
log = Log()
#functools.wraps(func)
def wrapper(*func_args, **func_kwargs):
# **here** I am missing something to replace 0 with the correct values!
saved_arguments = {a: 0 for a in save_args_names}
log.add(Call(name, **saved_arguments))
return_value = func(*func_args, **func_kwargs)
return return_value
return wrapper
To test this, I have the following functions set up:
def inner(x, add=0):
return sum(x) + add
def outer(number, add=0):
x = range(number)
return inner(x, add)
and the basic use case (no saving of arguments) works:
inner = record(inner)
print(outer(1), outer(2), outer(3))
print(Log())
It outputs, correctly:
0 1 3
Call(name=inner, saved_arguments={})
Call(name=inner, saved_arguments={})
Call(name=inner, saved_arguments={})
What I am missing is a way to have this use case:
inner = record(inner, save_args_names=['x'])
print(outer(1), outer(2), outer(3))
print(Log())
to output:
0 1 3
Call(name=inner, saved_arguments={'x': range(0, 1)})
Call(name=inner, saved_arguments={'x': range(0, 2)})
Call(name=inner, saved_arguments={'x': range(0, 3)})
This, should also work for keyword arguments, e.g.:
inner = record(inner, save_args_names=['x', 'add'])
print(outer(1, 2), outer(2, 3), outer(3, 4))
print(Log())
should output:
2 4 7
Call(name=inner, saved_arguments={'x': range(0, 1), 'add': 2})
Call(name=inner, saved_arguments={'x': range(0, 2), 'add': 3})
Call(name=inner, saved_arguments={'x': range(0, 3), 'add': 4})
I feel like I am close and that the inspect library should help me close this, but a little help would be much appreciated!
The function you're looking for is Signature.bind. Define your wrapperfunction like so:
#functools.wraps(func)
def wrapper(*func_args, **func_kwargs):
signature = inspect.signature(func)
bound_args = signature.bind(*func_args, **func_kwargs)
saved_arguments = {a: bound_args.arguments[a] for a in save_args_names}
log.add(Call(name, **saved_arguments))
return_value = func(*func_args, **func_kwargs)
return return_value

Categories

Resources