Conceptual understanding of variable scope in higher order functions - python

I have a function score0 which gives me a score for every turn (for n turns) in a loop. This score is incremented by a random integer from 1 to 15 every turn.
I now have to design another higher order function which should print the highest score jump of a player out of all the score jumps yet, and should be called inside the score0 function. I name it highest_gain. Naturally, this should print the first score value as it's the first turn (hence it is the biggest jump).
# Function that defines the highest point jump in a score yet
import random
def highest_gain(previous_value, highest_point):
def say(score) :
if previous_value == 0:
print ('Biggest gain by player0 yet with',score,'points!')
return highest_gain(score, score)
gain = score - previous_value
if gain > highest_point:
print('Biggest gain by player0 yet with',score,'points!')
return highest_gain(score, gain)
return say
# Function that gives me a new score (incremented) every turn
def score0(n, score = 0):
while n > 0:
score += random.randint(1, 15)
highest_gain(previous_value = 0,highest_point = 0)(score)
n -= 1
return score
#Calling the function
score0(4,0)
Python Tutor link
The problem is that calling highest_gain() doesn't update the values of previous_value and highest_point. Why aren't these variables getting updated in the score0() function body, and how should highest_gain() be called be called so that these variables are updated on each iteration of the loop?

Your highest_gain function is a higher-order function which returns another function named say. When say is called, it calls highest_gain again and returns the result, which is - again - the function say. The important point here is that say is a closure over the local variables of the outer function highest_gain, so each time highest_gain is called you get a different instance of the say function, with different values of the outer function's local variables.
Now, since calling say returns another instance of say which closes over the updated values, that means you need to keep the result from when you call it, so you can call the new instance which closes over those updated values.
def score0(n, score=0):
say = highest_gain(previous_value=0, highest_point=0)
while n > 0:
score += random.randint(1, 15)
say = say(score) or say
n -= 1
return score
I moved the original call of highest_gain to before the loop, since you don't want to use the initial values of 0 on every iteration.
Note that say doesn't always return a new instance of say - sometimes it returns None, so I used the trick say(score) or say here so that say keeps its old value if there isn't a new one to update it with. You could alternatively write this as below, which is more verbose but perhaps makes clearer what this is doing:
new_say = say(score)
if new_say is not None:
say = new_say
Otherwise, you could change the definition of say so that it returns itself (i.e. with the current values of the variables from the outer function) when there should be no update, and then in score0 you could just write say = say(score).

Related

Python random module doesn't randomize more than once

in this code there are two Random Number Generators. One in the first line, the other in the function btn_yes.
Both RNG's work fine, the print(nb) directly after the generator in the function btn_yes displays a random number like it should.
However, when btn_press is activated after btn_yes was activated (like it should in the program), the value of nb doesn't change, no matter how often i execute btn_yes. btn_press just uses the same number that was generated by the first RNG.
What am I missing?
nb = random.randrange(0, 11)
def btn_press():
guess = int(entry.get())
print(guess)
if guess < nb:
answ["text"] = "Higher!"
elif guess > nb:
answ["text"] = "Lower!"
elif guess == nb:
answ["text"] = "Correct!"
btn2["bg"] = "#FF6C6C"
btn3["bg"] = "#32FF00"
def btn_no():
window.destroy()
def btn_yes():
answ["text"] = "Next Round! Type in a new number!"
btn2["bg"] = "#464646"
btn3["bg"] = "#464646"
nb = random.randrange(0, 11)
entry.delete(0, tk.END)
print(nb)
The problem here is that when you change a global variable inside a function, you need to put global <variablename> as the first line in the function. Otherwise, Python assumes you meant to make a new variable with that name that is only in scope inside the function, and the global variable remains unchanged.
You can see this by printing nb inside the function btn_yes(); you should see that it has a different value each time (and not the same value as the global nb).
In this case, if you put global nb as the first line in btn_yes(), it should have the desired effect.
See using global variables in a function

Function vs if-statement: Function is not working, but the code in the function will work when outside a function?

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.

How to use multiple functions at once?

def add_course(gc, course):
global gpacalc
global creditcalc
gpacalc = gpacalc + (gc * course)
creditcalc = (course + creditcalc)
def gpa():
return (gpacalc/creditcalc)
def credit_total():
return(creditcalc)
I am trying to write a file named gpa.py that computes a cumulative GPA using three functions: add_course adds a new course to the running total, and gpa and credit_total gets your cumulative GPA and credit count, respectively. I am attempting to use two global variables to keep track of GPA and credits (both initially 0).
-Invoking gpa.add_course(3.7, 3) should add a 3-credit course with GPA 3.7 to the running GPA and credit count total.
-Invoking gpa.gpa() should retrieve your current total GPA.
-Invoking gpa.credit_total() should retrieve your current total credits earned.
-Invoking gpa.add_course with only one argument (e.g., gpa.add_course(3.7)) should add a 3-credit course.
What am I doing wrong within my code? I am trying to have both the functions nor the file itself containing any print or asking for any input.
Your are throwing a number of things into your code that don't behave the way you expect, because Python is interpreting things differently than you expect. I recommend stepping back and going through a Python tutorial such as learnpython.org, it'll save you a huge amount of time and hassle.
Given the approach you are taking, here is a version that works, with some comments on what I changed.
gpacalc = 0
creditcalc = 0
# If you want the course argument to be optional,
# you need to provide a default value for it.
def add_course(gc, course = 3):
global gpacalc
global creditcalc
# += is the concise way to increment a value:
gpacalc += gc * course
creditcalc += course
def gpa():
# No need to use parenthesis for this return value
# unless you intend the result to be in tuple form.
return gpacalc/creditcalc
def credit_total():
# Same here, no need for parenthesis.
return creditcalc
add_course(3.7, 3)
print(gpa()) # 3.7000000000000006
print(credit_total()) # 3
add_course(3.7)
print(credit_total()) # 6

Assigning a value to a objects created at run time (python)

I am trying to use a while loop to create object to populate a list of a user defined type until a certain condition is met. I want to assign a value to each object based on the number of iterations the loop has completed. For example:
class WalkingPeeps:
def___init___(self):
self.location = 0
def leftAt(self,time):
self.tleft = time
def changePos(self):
self.location += random.choice([1, -1])
objectList =[]
location_reached = False
time = 0
while not location_reached
objectList.append(WalkingPeeps())
for x in objectList:
x.tleft = time
if x.location == 20:
location_reached = True
time+=1
print("Person left at: ",x.tleft)
print("Person arrived at: ", time)
However, when it runs, it just set the time the object was created to one less than when the person reached 20. Any pointers? Hints? Thanks in advance.
In python, loops do not define their own scope. When you write
for x in objectList: ...
There variable x is created. At each step in the loop, the variable is updated. When the loop ends, the variable is not destroyed. Therefore, when you print x.tleft, you're printing the time on the last x, which by definition is 20, since you break the loop only when x.tleft == 20.
Furthermore, since you loop over every single element at each phase and update its time, you're setting each elements time to the most reccent time. Therefore, all elements have time == 20, when you terminate. What you mean, I believe, is to only update the last element
What I think you want to print, to check that your loop is working is,
for obj in objectList:
print( obj.tleft )
You would then see the expected behaviour
You also have many errors, including some syntax errors and some that make the code enter an infinite loop. This is the version I worked with, in good faith (try and make sure that the the only bugs in your code are the one's you're asking about!)
class WalkingPeeps: pass # None of the methods were relevant
objectList =[]
location_reached = False
time =0
while not location_reached:
objectList.append(WalkingPeeps())
x = objectList[-1]
x.tleft = time
# you need to check tleft, not location; location is never set
if x.tleft == 20:
location_reached = True
time+=1
print("Person left at: ",x.tleft)
print("Person arrived at: ", time)
for person in objectList: print(person.tleft)
A far more readable and concise version of this code would be:
class WalkingPerson:
def __init__(self,time=0):
self.time=time
objectList = [WalkingPerson(t) for t in range(20)]

Python: variable is undefined when calling function containing the variable

I just started my first programming class a few weeks ago and I'm embarrassed to say I'm very stuck. We had to create a program that (in my Professor's words):
Simulate the roll of two dice. Use a randomly generated integer to represent the roll of each die in a function named point. Return the combined value of a roll. Use a loop in main to roll the dice five times and report each result.
So, I did my best and keep getting the same issue where it is telling me my variable total is not defined, even though I'm calling the function which contains the variable.
I submitted the below code to my professor, who in turn responded:
The dice program is close. Return the total of a roll. Call point in main and capture the returned value for printing.
So he is saying to call the function point in my main function (which, at least I think, I am) but it still won't read my vital variable to finishing this.
import random
min=1
max=6
def main():
for roll in range(5):
point()
print(total)
def point():
roll=random.randint(min, max)
roll2=random.randint(min, max)
total=roll+roll2
return total
main()
Inside the main function, this line:
point()
does not make total available in the current scope. Instead, it simply calls the function point and then discards its return value.
You need to capture this return value by assigning it to a variable named total:
def main():
for roll in range(5):
################
total = point()
################
print(total)
Now, when you do print(total), total will be defined and equal to the value of point().
You're trying to reference a variable that only exists within the scope of the point() function. But you don't need to since you return the value anyway.
Try this.
from random import randint
def rollD6():
return randint(1,6)
def point():
return rollD6()+rollD6()
def main():
for roll in range(5):
print point()
main()

Categories

Resources