I am learning the concepts of decorators in Python. While I think I have covered lots of blogs and have got some basic understanding how decorators work and why would we need one.
While doing so, I encountered a tutorial (totally lost the reference to this), where while explaining about decorators, the author wrote the following:
def call_counter(func):
def helper(x):
helper.calls += 1
return func(x)
helper.calls = 0
return helper
def succ(x):
return x + 1
succ = call_counter(succ)
print(succ.calls)
for i in range(10):
succ(i)
print(succ.calls)
Output:
0
10
As I mentioned earlier, I thought I understand how a decorator works and basic Python but suddenly am doubting myself here.
In helper function, the author is incrementing helper.calls variable by 1 (which should be, as per my previous understanding, just an ordinary variable and helper. is only to show this variable is inside helper function), but has only defined helper.calls after using it here , towards the end.
Similarly, towards end, after calling the decorator function, the author has suddenly used succ.calls variable - which is even printing expected result - but without any definition anywhere.
P.S. I have tried doing my research but could not even put this behaviour in proper words to find anything appropriate.
P.P.S. Could not come up with a more precise header as totally unfamiliar with the actual phenomenon happening.
succ = call_counter(succ) # succ.calls = 0 when function is called (succ = helper)
print(succ.calls) # prints 0
for i in range(10):
succ(i) # As succ = helper now, this becomes helper(i) and helper.i is incremented by 1 each iteration, succ(x) is then returned after as func = succ and x + 1 is returned from succ(x)
print(succ.calls) # prints 10 as helper.calls is incremented 10 times within the loop
The same output would have been achieved if succ(x) had returned no value as it was not used anywhere else, you can see your code visualized on http://www.pythontutor.com/visualize.html#mode=edit
Related
I was working on building a randomized character generator for Pathfinder 3.5 and got stuck.
I am using the Populate_Skills(Skill_String, Draw, Skill_List, Class_Skill): function to populate a randiomized list of skills with their class based points total, class bonus, and point buy. So modelling the action of a player picking skills for their character.
As an example below, Wizards.
I pick Knowledge_Arcana as a skill and spend one of my skill point pool (Calculated by taking my intelligence modifier +2) on it. So that skill now equals my intelligence modifier(+1 in this case), class skill bonus as a wizard (+3), plus the point I spent(+1) for a total of 5.
The problem is while the function prints the correct result of 5, the outstanding variables do not populate with the final total. To continue our example I'd run the function on Knowledge_Arcana, get a +5, and then check the Knowledge_Arcana after the function call and get just +1. Conversely, if I write out the function as just an if statement it works. Example is next to the function for comparison.
Does anyone know why Im getting the different result?
## Creating the lists and breaking into two separate sections
Int_Mod = 1
Skill_Ranks = 3
Rand_Class = 'Wizard'
Knowledge_Arcana = Int_Mod
Knowledge_Dungeoneering = Int_Mod
Wizard_Class_Top_Skills = ["Knowledge_Arcana"]
Wizard_Class_Less_Skills = ["Knowledge_Dungeoneering"]
Class_Skill = 3
Important_Skills_Weighted = .6
Less_Important_Skills_Weighted = .4
Important_Skills_Total_Weighted = round(Skill_Ranks*Important_Skills_Weighted)
Less_Skill_Total_Weighted = round(Skill_Ranks*Less_Important_Skills_Weighted)
Wiz_Draw =['Knowledge_Arcana', 'Knowledge_Dungeoneering']
def Populate_Skills(Skill_String, Draw, Skill_List, Class_Skill):
if Skill_String in Draw:
Skill_List = Skill_List + Class_Skill + Draw.count(Skill_String)
print(Skill_String, Skill_List)
else:
print('Nuts!')
## Function Calls
Populate_Skills('Knowledge_Arcana', Wiz_Draw, Knowledge_Arcana, Class_Skill)
Populate_Skills('Knowledge_Dungeoneering', Wiz_Draw, Knowledge_Dungeoneering, Class_Skill)
print(Knowledge_Arcana,Knowledge_Dungeoneering)
Edited to be a MRE, I believe. Sorry folks, Im new.
You are passing in a reference to a list and expect the function to modify it; but you are reassigning the variable inside the function which creates a local variable, which is then lost when the function is exited. You want to manipulate the same variable which the caller passed in, instead.
def Populate_Skills(Skill_String, Draw, Skill_List, Class_Skill):
if Skill_String in Draw:
Skill_List.extend(Class_Skill + Draw.count(Skill_String))
print(Skill_String, Skill_List)
else:
print('Nuts!')
Alternatively, have the function return the new value, and mandate for the caller to pick it up and assign it to the variable.
def Populate_Skills(Skill_String, Draw, Skill_List, Class_Skill):
if Skill_String in Draw:
Skill_List = Skill_List + Class_Skill + Draw.count(Skill_String)
print(Skill_String, Skill_List)
else:
print('Nuts!')
return Skill_List
Skill_List = Populate_Skills('Knowledge_Arcana', Wiz_Draw, Knowledge_Arcana, Class_Skill)
# etc
You should probably also rename your variables (capital letters should be used for classes and globals; regular Python functions and variables should use snake_case) and avoid using global variables at all. The entire program looks like you should probably look into refactoring it into objects, but that's far beyond the scope of what you are asking.
List item
I'm working on a code that calculates the 'distance' between two configurations of a Flip Cube, The distance between two configurations x and y is the minimum number of steps required to go from x to y, or conversely.
To make that I've created a simpler version that makes something different, this code takes two integer numbers ci and cf. with ci returns an iterable called main_level through the generator called multi, then, it iterates through it searching for the parameter cf, whenever cf is not in main_level the variable steps is increased by 1 and for each element in main_level we repeat the same process done for ci. Finally, when cii==cf the program ends and returns the steps variable, which counts the number of "levels" that we have to go down to find the given parameter cf. This code doesn't have any practical purpose is just a base for the problem I mentioned above.
If I call the distance(ci, cf) function with ci=5, the first two levels are:
{0,3,6,9,12} <-- first level (steps is initialized with 1)
if cf is any of the numbers in the set, the program should end and return steps=1,
if cf is not in that set, the programs form the second level:
{15,18,21,24,27,30,33} and search cf, if cf is there, the program ends and should return steps=2, if not, it forms the third level, and so on. But there is a problem, actually, when I call the distance function with ci=5 and cf= any natural number, and print its value, anything is output, only for cf=0, it outputs step=1. I don't really know what's going on. I would appreciate your help.
Here is the code:
#Base solution to FlipCube problem
def multi(par):
for i in range(par):
yield i*3
steps=1
def distance(ci,cf):
main_level =set(multi(ci))
global steps
def check_main_level(cf):
global steps
nonlocal main_level
def lower_level(config_list):
sett=set()
for i in config_list:
sett.update(q for q in multi(i) if q not in config_list)
nonlocal main_level
main_level=sett
check_main_level(cf)
for i in main_level:
if i==cf:
break
else:
steps+=1
lower_level(main_level)
check_main_level(cf)
return steps
#testing
e= distance(5,0)
print(e)# prints 1, very good
e2= distance(5,9)
print(e2)# should print 1, but doesn't print anything :(
e3= distance(5,27)
print(e3)# should print 2, but doesn't print anything :(
The program does not terminate recursion under all circumstances. The culprit seems to be the for loop in check_main_level. Change the code after your definition of lower_level to:
# code portion of check_main_level
if cf > max(main_level):
steps+=1
lower_level(main_level)
# end of code portion check_main_level (replacing for-loop)
you have an infinity loop, that's why nothing is printed.
You can see it easyly by adding a print :
for i in config_list:
print(i)
sett=set()
sett.update(q for q in list(multi(i)) if q not in config_list)
I have written an instance method which uses recursion to find a certain solution. It works perfectly fine except the time when I'm exiting the if-elif block. I call the function itself inside IF block. Also, I have only one return statement. The output from the method is weird for me to understand. Here is the code and the output:
def create_schedule(self):
"""
Creates the day scedule for the crew based on the crew_dict passed.
"""
sched_output = ScheduleOutput()
assigned_assignements = []
for i in self.crew_list:
assigned_assignements.extend(i.list_of_patients)
rest_of_items = []
for item in self.job.list_of_patients:
if item not in assigned_assignements:
rest_of_items.append(item)
print("Rest of the items are:", len(rest_of_items))
if len(rest_of_items) != 0:
assignment = sorted(rest_of_items, key=lambda x: x.window_open)[0]
# print("\nNext assignment to be taken ", assignment)
output = self.next_task_eligibility(assignment, self.crew_list)
if len(output) != 0:
output_sorted = sorted(output, key=itemgetter(2))
crew_to_assign = output_sorted[0][1]
assignment.eta = output_sorted[0][4]
assignment.etd = int(assignment.eta) + int(assignment.care_duration)
crew = next((x for x in self.crew_list if x.crew_number == crew_to_assign), None)
self.crew_list.remove(crew)
crew.list_of_patients.append(assignment)
crew.time_spent = assignment.etd
self.crew_list.append(crew)
self.create_schedule()
else:
print("*" * 80, "\n", "*" * 80, "\nWe were not able to assign a task so stopped.\n", "*" * 80, "\n", "*" * 80)
sched_output.crew_output = self.crew_list
sched_output.patients_left = len(rest_of_items)
elif not rest_of_items:
print("Fully solved.")
sched_output.crew_output = self.crew_list
sched_output.patients_left = 0
print("After completely solving coming here.")
return sched_output
This was the output:
Rest of the items are: 10
Rest of the items are: 9
Rest of the items are: 8
Rest of the items are: 7
Rest of the items are: 6
Rest of the items are: 5
Rest of the items are: 4
Rest of the items are: 3
Rest of the items are: 2
Rest of the items are: 1
Rest of the items are: 0
Fully solved.
After completely solving coming here.
After completely solving coming here.
After completely solving coming here.
After completely solving coming here.
After completely solving coming here.
After completely solving coming here.
After completely solving coming here.
After completely solving coming here.
After completely solving coming here.
After completely solving coming here.
After completely solving coming here.
What I don't understand is that as soon as my list rest_of_items is empty, I assign data to sched_output and return it. However, print statement is being executed for the same number of time as recursion was done. How can I avoid this?
My output is perfectly fine. All I want to do is understand the cause of this behaviour and how to avoid it.
The reason it's printing out 11 times is that you always call print at the end of the function, and you're calling the function 11 times. (It's really the same reason you get Rest of the items are: … 11 times, which should be a lot more obvious.)
Often, the best solution is to redesign things so instead of doing "side effects" like print inside the function, you just return a value, and the caller can then do whatever side effects it wants with the result. In that case, it doesn't matter that you're calling print 11 times; the print will only happen once, in the caller.
If that isn't possible, you can change this so that you only print something when you're at the top of the stack. But in many recursive functions, there's no obvious way to figure that out without passing down more information:
def create_schedule(self, depth=0):
# etc.
self.create_schedule(depth+1)
# etc.
if not depth:
print('After completely solving come here.')
returns sched_output
The last resort is to just wrap the recursive function, like this:
def _create_schedule(self):
# etc.
self._create_schedule()
# etc.
# don't call print
return sched_output
def create_schedule(self):
result = self._create_schedule()
print('After completely solving come here.')
return result
That's usually only necessary when you need to do some one-time setup for the recursive process, but here you want to do some one-time post-processing instead, which is basically the same problem, so it can be solved the same way.
(Of course this is really just the first solution in disguise, but it's hidden inside the implementation of create_schedule, so you don't need to change the interface that the callers see.)
As you call your create_schedule function within itself before the function finishes, once it has gotten to the end and doesn't need to call itself again, each function ends, and hits the "After completely solving coming here.", at the end of the function.
This means that each function, after calling itself, is still running - just stuck at the line where it calls itself - until they have all completed, which is when the paused functions can finish their task, printing out your statement.
You have print("After completely solving coming here.") at the end of your recursive function. That line will be executed once for each recursion.
Consider this simple example, which recreates your issue:
def foo(x):
print("x = {x}".format(x=x))
if x > 1:
foo(x-1)
print("Done.")
Now call the function:
>>> foo(5)
x = 5
x = 4
x = 3
x = 2
x = 1
Done.
Done.
Done.
Done.
Done.
As you can see, on the final call to foo(x=0), it will print "Done.". At that point, the function will return to the previous call, which will also print "Done." and so on.
EDIT:
Hi, earlier, I posted this question in a way that very lengthy. I am new to this site and did not know about the customs of posting. I will try again and be more specific with my question.
I moved a bit of my code into a tester file and this part returns an error. It seems that my count for blackpegs is not increasing because local variable 'blackpegs' was references before assignment.
blackpegs = 0
def test_guess(code,guess):
for x,y in zip(code,guess):
if x==y:
blackpegs += 1
return blackpegs
First, what does this mean? Secondly, how do I fix it?
You should move the variable definition to within the function definition. I'm guessing you also want the "return" statement to be indented at the same level as the if statement, so that it goes through the for loop rather than exiting after the first instance where x==y.
def test_guess(code,guess):
blackpegs = 0
for x,y in zip(code,guess):
if x==y:
blackpegs += 1
return blackpegs
dear expert i am trying to write a simulation and in my code i have a class like this:
... (some def are here)
class multipole:
global xxp,yyp,zzp,x,y,z,xp,yp,zp,t,tm,h
xxp,yyp,zzp,x,y,z,xp,yp,zp =xxpyypzzp() # some initial values calling
#staticmethod
def quad(f1, f2,f3):
global t,h,mass,ksimax
while t < ksimax:
rk4_two(t,h,mass, f1, f2, f3, xxp, yyp, zzp) # rk function for new xxp, yyp and zzp
t = t + h
tm.append(t)
xp.append(xxp[1])
x.append(xxp[0])
yp.append(yyp[1])
y.append(yyp[0])
zp.append(zzp[1])
z.append(zzp[0])
return xp, x, yp,y,zp,z,tm
if __name__ == "__main__":
qp=multipole()
quxp, qux, quyp,quy,quzp,quz,qutm=qp.quad(0.,0.,0.)
hxp, hx, hyp,hy,hzp,hz,htm =qp.quad(0.022,0.,0.)
oxp, ox, oyp,oy,ozp,oz,otm =qp.quad(-0.023,-0.032,0.0 )
my question is this code only calculate (quxp, qux, quyp,quy,quzp,quz,qutm), but not others (others will turn same value of quxp, qux, quyp,quy,quzp,quz,qutm) could you please tell me why? i am new in python any comments will be appreciated.
Ignoring the fact that this code is... somewhat flawed. I think that the problem is that you are using t which is apparently global but you don't reset it anywhere - so this loop:
while t < ksimax:
...
Will only run once, unless you reset t somewhere. Some pseudo code to explain why this happens:
counter = 0
def do_something():
global counter
print "Starting at", counter
while counter <= 10:
print counter
counter += 5
print "Done"
do_something()
# Starting at 0
# 0
# 5
# 10
# Done
do_something() # Called again, the counter is at 10 now:
# Starting at 10
# Done
As others have mentioned, your code could benefit from some heavy refactoring. Some starting points:
Naming! What does xxpyypzzp even mean? Even if it's obvious to you today, it must be hard to read even for you and unless you have Rainman-like memory you will not understand this next week. Try using descriptive names and if you find yourself adding prefixes or suffixes to variables because you run out of names - think about encapsulating some of this complexity in a class. It seems like the suffixes xp, x, yp, y, zp, z and tm are used a lot. At least create a named tuple to hold these values.
Global variables is generally considered harmful. They make it hard to reason about code - which is why you got this bug in the first place. If you are sprinkling global statements over your code there is time to redesign it. Think about which part of your code should "own" which parts of the state.
Python has a coding standard, called PEP8 - read it and try to follow it.