invalid literal int base 10 occurring sporadically with the same variable - python-3.x

I have a Python3.4.2/tkinter program that requests a numeric user-entry which I have as a StringVar: my issue is that sometimes( probably about 30% of the time) it returns the invalid literal int base 10 and sometimes it functions perfectly. When the base 10 issue occurs I have to close the app and reopen. Printing the StringVar always returns PY_VAR0 and from within the functions that use the StringVar, when the app functions, the number that was entered returns. When the base 10 issue occurs, the app 'crashes' returning only the PY_VAR0 and then the error message. If it were consistent I would understand where to hunt my error but with it behaving apparently irrationally I don't know how to start.
How do I go about investigating this issue?
The assignment part this:
to_choose = StringVar()
#print(to_choose)
text = "\n{0}\n {1}".format("Enter number", "of Draws", pady = 10)
instruction = Label(actions_label_frame, text =text, bg = 'dark sea green', fg = 'dark slate gray')
instruction.grid(column = 2, row= 2, sticky = N)
how_many_draws_entry = Entry(actions_label_frame, text = "", bd = 2, width = 3, bg = 'mint cream', fg = 'sea green', textvariable = to_choose, cursor = 'arrow', highlightcolor ='red')
The section that attempts to use the variable:
mains_results = []
draw = 0 # identifier for draw number to separate results clearly
main_num_draws = int(to_choose.get())
print("from main", main_num_draws)
# check for empty string
'''
try:
main_num_draws = int(to_choose.get())
except ValueError:
main_num_draws = 1
'''
while draw < main_num_draws: # loop 1
merged = list(itertools.chain.from_iterable(final_list)) # flatten list
# print(len(merged)) #check length of list for pseudocheck
random.shuffle(merged)
chosen = [] # accumulate eack pick in this list: reset here as well
draw += 1 # increment draw identifier
while len(chosen) <= 5: # loop 2, to choose the five numbers of a draw
pick = random.choice(merged)
chosen.append(pick)
merged = list(filter((pick).__ne__, merged)) # remove draw from choices(numbers)
#print(len(merged)) #pseudocheck that values have been removed
chosen.sort()
mains_results.append(chosen)
and the error message:
main_num_draws = int(to_choose.get())
ValueError: invalid literal for int() with base 10: ''

The error message is telling you exactly what's wrong. When you do this:
main_num_draws = int(to_choose.get())
... the error message is telling you that to_choose.get() is returning an empty string. An empty string is an invalid integer.
You need to either guarantee that the variable always has a valid integer in it, or you need to catch that error and handle it appropriately. Oddly, you have commented out code that does exactly that.
If you print one of these variables and see something like PY_VAR0, that just means you are doing something like print the_variable rather than print the_variable.get(). The string representation of the object isn't the value, it's a unique internal name (dumb design decision on the part of Tkinter designers IMO, but it is what it is).

Related

Keeping the same distance no matter the string length [duplicate]

I'm sure this is covered in plenty of places, but I don't know the exact name of the action I'm trying to do so I can't really look it up. I've been reading an official Python book for 30 minutes trying to find out how to do this.
Problem: I need to put a string in a certain length "field".
For example, if the name field was 15 characters long, and my name was John, I would get "John" followed by 11 spaces to create the 15 character field.
I need this to work for any string put in for the variable "name".
I know it will likely be some form of formatting, but I can't find the exact way to do this. Help would be appreciated.
This is super simple with format:
>>> a = "John"
>>> "{:<15}".format(a)
'John '
You can use the ljust method on strings.
>>> name = 'John'
>>> name.ljust(15)
'John '
Note that if the name is longer than 15 characters, ljust won't truncate it. If you want to end up with exactly 15 characters, you can slice the resulting string:
>>> name.ljust(15)[:15]
If you have python version 3.6 or higher you can use f strings
>>> string = "John"
>>> f"{string:<15}"
'John '
Or if you'd like it to the left
>>> f"{string:>15}"
' John'
Centered
>>> f"{string:^15}"
' John '
For more variations, feel free to check out the docs: https://docs.python.org/3/library/string.html#format-string-syntax
You can use rjust and ljust functions to add specific characters before or after a string to reach a specific length.
The first parameter those methods is the total character number after transforming the string.
Right justified (add to the left)
numStr = '69'
numStr = numStr.rjust(5, '*')
The result is ***69
Left justified (add to the right)
And for the left:
numStr = '69'
numStr = numStr.ljust(3, '#')
The result will be 69#
Fill with Leading Zeros
Also to add zeros you can simply use:
numstr.zfill(8)
Which gives you 00000069 as the result.
string = ""
name = raw_input() #The value at the field
length = input() #the length of the field
string += name
string += " "*(length-len(name)) # Add extra spaces
This will add the number of spaces needed, provided the field has length >= the length of the name provided
name = "John" // your variable
result = (name+" ")[:15] # this adds 15 spaces to the "name"
# but cuts it at 15 characters
I know this is a bit of an old question, but I've ended up making my own little class for it.
Might be useful to someone so I'll stick it up. I used a class variable, which is inherently persistent, to ensure sufficient whitespace was added to clear any old lines. See below:
2021-03-02 update: Improved a bit - when working through a large codebase, you know whether the line you are writing is one you care about or not, but you don't know what was previously written to the console and whether you want to retain it.
This update takes care of that, a class variable you update when writing to the console keeps track of whether the line you are currently writing is one you want to keep, or allow overwriting later on.
class consolePrinter():
'''
Class to write to the console
Objective is to make it easy to write to console, with user able to
overwrite previous line (or not)
'''
# -------------------------------------------------------------------------
#Class variables
stringLen = 0
overwriteLine = False
# -------------------------------------------------------------------------
# -------------------------------------------------------------------------
def writeline(stringIn, overwriteThisLine=False):
import sys
#Get length of stringIn and update stringLen if needed
if len(stringIn) > consolePrinter.stringLen:
consolePrinter.stringLen = len(stringIn)+1
ctrlString = "{:<"+str(consolePrinter.stringLen)+"}"
prevOverwriteLine = consolePrinter.overwriteLine
if prevOverwriteLine:
#Previous line entry can be overwritten, so do so
sys.stdout.write("\r" + ctrlString.format(stringIn))
else:
#Previous line entry cannot be overwritten, take a new line
sys.stdout.write("\n" + stringIn)
sys.stdout.flush()
#Update the class variable for prevOverwriteLine
consolePrinter.overwriteLine = overwriteThisLine
return
Which then is called via:
consolePrinter.writeline("text here", True)
If you want this line to be overwriteable
consolePrinter.writeline("text here",False)
if you don't.
Note, for it to work right, all messages pushed to the console would need to be through consolePrinter.writeline.
I generally recommend the f-string/format version, but sometimes you have a tuple, need, or want to use printf-style instead. I did this time and decided to use this:
>>> res = (1280, 720)
>>> '%04sx%04s' % res
'1280x 720'
Thought it was a touch more readable than the format version:
>>> f'{res[0]:>4}x{res[1]:>4}'
First check to see if the string's length needs to be shortened, then add spaces until it is as long as the field length.
fieldLength = 15
string1 = string1[0:15] # If it needs to be shortened, shorten it
while len(string1) < fieldLength:
rand += " "
Just whipped this up for my problem, it just adds a space until the length of string is more than the min_length you give it.
def format_string(str, min_length):
while len(str) < min_length:
str += " "
return str

Problem with decoding bytes to text and comparison?

I do not understand why when you open a document in bytes format with the 'open' function and decode it to text, when compared to a variable that contains exactly the same text, python says they are different. But that only happens when the decoded text of the document has line breaks.
example:
o = open('New.py','rb')
t = o.read().decode()
x = '''this is a
message for test'''
if t == x:
print('true')
else:
print('false')
Although the decoded text 't' and the text of the 'x' are exactly the same, python recognizes them as different and prints false.
I have really tried to find the difference in many ways but I still don't understand how they differ and how I can convert 't' to equal 'x'?
It's because the line breaks are still part of the string (represented as \n) even if you don't see it.
import binascii
o = open('new.py','rb')
t = o.read().decode()
print(binascii.hexlify(t.encode()))
# b'7468697320697320610a6d65737361676520666f7220746573740a'
x = '''this is a
message for test'''
print(binascii.hexlify(x.encode()))
# b'7468697320697320610a6d65737361676520666f722074657374'
Here, 0x0a at the end of t is the byte representation for the new line.
To make them the same, you need to strip out whitespaces and new lines. Assuming new.py looks like this (same as the value for x):
this is a
message for test
Then just do this:
o = open('new.py','rb')
t = o.read().decode().strip()
x = '''this is a
message for test'''
if t == x:
print('true')
else:
print('false')

about nested function unboundlocalerror in python3

here is my function with 3 arguments of which two are with default values, and there is a nested function within this function.
def announce_highest(who=1, previous_high=0, previous_score=0):
assert who == 0 or who == 1, 'The who argument should indicate a player.'
# BEGIN PROBLEM 7
"*** YOUR CODE HERE ***"
sentence = "That's the biggest gain yet for Player"
def say_highest(score0, score1):
#nonlocal previous_score, previous_high
print(who, previous_score)
if who == 0:
target = score0
else:
target = score1
score_gap = target - previous_score
if score_gap > previous_high:
print(score_gap, "point" + 's' *(1 - (1 // score_gap)) + '!', sentence, who)
previous_high = score_gap
previous_score = target
return announce_highest(who)
return say_highest
f0 = announce_highest() # Only announce Player 1 score gains
f1 = f0(11, 0)
When i do the assignment to f0, it runs ok. But when doing the assignment to f1, it raised a unboundlocal error:local variable 'previous_score' referenced before assignment.
Why I do something to the argument 'who'(such as print it), it runs normally, but to the argument 'previous_high' and 'previous_score' with same operation, unboundlocal error……
why does this happen? Did not 'who' and 'previous_score' have the same scope?
I got the answer from the professor John. This error is caused by the following assignment statement 'previous_score = target'. Once an assignment of "previous_score" exsits somewhere within the function defined in another function, the previous "previous_score" in the parent frame is no longer accessible.
That's why it raised an error.

String index out of range and I can't fix the error

So below is my code. Somewhere in the "for digit in observed" loop there is an indexing error that I can't seem to find or fix. I believe it's something to do with the PINS list, but I'm not sure, as none of my edits have made a difference. Test case that fails is observed = '11'. Single cases all pass. Unfortunately as I'm using codewars, there is no line given for the error, just the following:
Traceback:
in
in get_pins
IndexError: string index out of range
def get_pins(observed):
# Let's see what we're working with
print("observed")
print(observed)
print(" ")
# Dictionary of possible numbers for a half-assed observation of a key press.
possible = {'0':'08','1':'124','2':'1235','3':'236',
'4':'1457','5':'24568','6':'3569',
'7':'478','8':'05789','9':'689'}
# Single digit pwd case
PINS=[]
if len(observed) == 1:
for digit in possible[observed]:
PINS.append(digit)
return PINS
# Find number of possible PINs
num_possibles = 1
# Step through observed digits
for digit in observed:
num_possibles*=len(possible[digit])
# Populate PINS to allow string manipulation
PINS = " "*num_possibles
print(PINS[num_possibles])
num_change = num_possibles
change = []
count = 0
# Step through observed, determine change of digit,
for digit in observed:
# Last digit in observed means it iterates every time
if digit != observed[len(observed)-1]:
# Develop array for checking position
num_change = num_change/len(possible[digit])
for i in range(1,len(possible[digit])):
change.append(i*num_change)
print(change)
# Populate PINS with possible digit, full pin is created after final iteration of digit/observed loop
for pin in range(0,num_possibles-1):
PINS[pin] = PINS[pin] + possible[digit][count]
if (pin+1) in change:
count+=1
change=[]
count =0
else:
for pin in range(0,num_possibles-1):
PINS[pin] = PINS[pin] + possible[digit][count]
count+=1
if count == len(possible[digit]):
count = 0
return PINS
The problem is here:
PINS = " "*num_possibles
print(PINS[num_possibles])
The first line creates a string of spaces of length num_possibles. This means that the valid indices are 0, 1, ..., num_possibles - 1. However, in the very next line you try to index the string at the non-existent index num_possibles.
I would simply drop that print. What is the point of it? You know that PINS is a string of all spaces, so why bother?
Strings are immutable, since PINS is a string, the line
PINS[pin] = PINS[pin] + possible[digit][count]
would trigger the error:
TypeError: 'str' object does not support item assignment
What you should do is to initialize PINS as
PINS = [' ']*num_possibles
Or, probably even better
PINS = ['']*num_possibles
In which case
PINS[pin] = PINS[pin] + possible[digit][count]
would be legal (although it could be shortened by use of +=) -- although I'm not sure if this is what you really want to do since you are concatenating the strings stored as values in the possible and not adding the numbers represented by those strings.
At the end of the function, replace return PINS by return''.join(PINS)`
That will fix some of your bugs, but since I know neither intended input nor intended output I can't say any more.

Pygame 3.3 Not updating screen correctly leading to freeze

Within my program I have a Popup object that upon being created, draws a message to the screen, pauses, and then removes it. This works absolutely fine most of the time, including when messages are chained together one after the other. However, if at any point the game window comes out of focus for any reason (it is minimized or you click on another window), then after one or two chained popups, the screen will freeze and not show any subsequent ones, becoming responsive again after the last popup would have disappeared. This appears to occur just before the popup disappears, as the one it gets stuck on remains visible for the duration of the freeze.
I have no idea if this is a problem with Python or Pygame, but I'm hoping it's something I've done so it can be fixed. Does anyone have any idea what is happening?
class Popup():
def __init__(self,surface,font,colour,thickness,text,textcolour,duration,align="center",x=None,y=None): #Position can be specified, but will default to centre of screen.
self.surface = surface
self.font = font
self.colour = colour
self.x = x
self.y = y
self.thickness = thickness
self.text = text
self.textcolour = textcolour
self.duration = duration
self.align = align
self.appear()
sleep(self.duration) #I want to thread this if I can. For now, it pauses the entire program, so don't use long durations!
self.disappear()
def appear(self):
screenBorder = pygame.Rect(0,0,self.surface.get_width()-10,50) #Limits text width to slightly smaller than screen.
message = multiLineText(None,self.font,self.text,self.textcolour,screenBorder,self.align)
POPUPSURF = pygame.Surface((message.get_width()+(self.thickness*2),message.get_height()+(self.thickness*2))).convert_alpha() #New surface size of message plus border.
POPUPSURF.fill(self.colour)
POPUPSURF.blit(message,(self.thickness,self.thickness)) #Add the text.
if self.thickness > 0:
alphas = []
increment = int(255/self.thickness)
for i in range(self.thickness):
alphas.append(increment*(i+1))
for alpha in alphas:
num = alphas.index(alpha)
thisL = POPUPSURF.get_width() - (2*num)
thisH = POPUPSURF.get_height() - (2*num)
R,G,B = self.colour
pygame.draw.rect(POPUPSURF,(R,G,B,alpha),(num,num,thisL,thisH),1) #Draw border.
if self.x == None:
self.x = (self.surface.get_width()/2) - (POPUPSURF.get_width()/2)
if self.y == None:
self.y = (self.surface.get_height()/2) - (POPUPSURF.get_height()/2)
self.BACKUPSURF = pygame.Surface((POPUPSURF.get_width(),POPUPSURF.get_height()))
self.BACKUPSURF.blit(self.surface,(0,0),(self.x,self.y,POPUPSURF.get_width(),POPUPSURF.get_height())) #Takes a copy of what it looks like without popup. This can be restored in disappear().
self.surface.blit(POPUPSURF,(self.x,self.y))
pygame.display.update()
def disappear(self):
self.surface.blit(self.BACKUPSURF,(self.x,self.y))
pygame.display.update()
sleep(0.1)
Example of usage:
expGainPopup = Popup(DISPLAYSURF,font,DARKRED,10,"You have gained %d EXP!" %gain,WHITE,DURATION) #Inform of gain.
if player["Level"] < 15: #If not max level (would cause out-of-bounds error on array)
progressPopup = Popup(DISPLAYSURF,font,DARKRED,10,"You now have %d EXP out of %d to level up." %(player["EXP"], levels[player["Level"]]),WHITE,DURATION) #Inform of progress.
If anyone wants to test the code, they will also need the MultiLineText function:
def multiLineText(background,font,text,colour,boundary=None,align="left",returnNum=False): #NB the boundary ONLY cares about width/length. It will not limit the height of returned surface objects.
if type(boundary) == pygame.Rect: #If a valid boundary is passed
testSection = ""
testResult = ""
for word in findall(r'\S+|\n',text): #Borrowed this from my text compressor program. Splits all words and newlines, as \S+ finds a non-whitespace character and all similar characters following it. \n will match all newlines, treating them as words. | is just the 'or' operator. r makes it a raw string so Python doesn't get confused with all the formatting symbols.
testSection += " "+word #Remember to add spaces back in!
if word == "\n":
testResult += testSection #Skip check and auto-newline if it is a newline
testSection = ""
elif font.size(testSection)[0] > boundary.width: #If has exceeded one line length
testSection = testSection[:-len(word)-1] #Remove the word, -1 also removes the space before the word.
testResult += testSection+"\n" #Add string with line break to the result.
testSection = " "+word #Restart testSection with word that did not fit, ready for new line.
testResult += testSection #Add the last section that never reached the max length to the output so it doesn't get lost.
lines = testResult.split("\n")
for i in range(len(lines)):
lines[i] = lines[i][1:] #Removes the first character, an unwanted space.
else: #If just working off existing \n in the text
lines = text.split("\n")
lengths = []
for line in lines:
lengths.append(font.size(line)[0])
length = max(lengths) #Length is set to the length of the longest individual line.
totalHeight = 0
for line in lines:
totalHeight += font.size(line)[1]
TEXTSURF = pygame.Surface((length,totalHeight)).convert_alpha()
if background != None: #Fill the background with colour or transparency.
TEXTSURF.fill(background)
else:
TEXTSURF.fill((0,0,0,0))
for i in range(len(lines)):
lines[i] += str(i) #Add a unique identifier onto every line. This way if two or more lines are the same, index(line) will not return the wrong value, resulting in repeated lines not appearing.
for line in lines:
dudChars = len(str(lines.index(line))) #The number of characters in the index of this line within lines, and therefore the character length of the unique identifier we want to remove.
renderedLine = font.render(line[:-dudChars],True,colour) #Render the line without identifier.
if align == "left":
xpos = 0
elif align == "center":
xpos = (length/2) - (renderedLine.get_width()/2)
elif align == "right":
xpos = length - renderedLine.get_width()
ypos = int((lines.index(line)) * (totalHeight/len(lines))) #Find the appropriate vertical space
TEXTSURF.blit(renderedLine,(xpos,ypos)) #Add to the multiline surface.
if returnNum == False:
return TEXTSURF
else:
return TEXTSURF, len(lines) #Return the number of lines used as well, if it was asked for.
This was eventually solved by adding in the pygame.event.pump function into key places. This allows pygame to process its internal events such as talking with the OS. Without this, the event queue is paused for too long and the program gets flagged as not responding, the screen freezes, and bad things happen.
Relevant fixed code (only one line different):
def disappear(self):
self.surface.blit(self.BACKUPSURF,(self.x,self.y))
pygame.display.update()
pygame.event.pump() #So the display updates properly, stops it from freezing and making you miss messages. This allows pygame to do some internal events such as talking to the OS. Otherwise, if the event queue has been paused for too long, the OS will treat it as not responding, and bad things happen.
sleep(0.1)

Resources