Entry Validation going wrong with tkinter - python-3.x

I am currently learning the tkinter basics and I'm building a small, super-simple program to test my knowledge on some of the most basic widgets.
I am having a problem with validation and an entry, possibly because of my lack of understanding in the matter... This poses three questions:
1 - How to do what was done here: https://stackoverflow.com/a/4140988/2828287 without the class part. Just doing it when the script runs.
2 - What are all those self. and .self doing there? Which ones are there because that is a class, and which ones are there because of the validating method itself??
3 - What's wrong in my code? based in this explanation >> http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/entry-validation.html
from tkinter import *
from tkinter import ttk
# function that should take the '%d' replacer and only validate if the user didn't delete
def isOkay(self, why):
if why == 0:
return False
else:
return True
okay = entry.register(isOkay) # didn't understand why I had to do this, but did it anyway...
entry = ttk.Entry(mainframe, validate="key", validatecommand=(okay, '%d'))
# the mainframe above is a ttk.Frame that contains all the widgets, and is the only child of the usual root [ = Tk()]
entry.grid(column=1,row=10) # already have lots of stuff on upper rows
The error I'm getting goes like this:
"NameError: name 'entry' is not defined"
I've tried to change the order of things, but there's always one of these errors.. It points to the line where I do the .register() stuff
--EDITED CODE--
This doesn't throw me an error, but still allows me to delete...
def isOkay(why):
if (why == 0):
return False
else:
return True
okay = (**root**.register(isOkay), "%d")
entry = ttk.Entry(mainframe, validate="key", validatecommand=okay)
entry.grid(column=1,row=10)
(where the 'root' part is written between ** **, does it have to be the root? Or it can be any parent of the widget that is going to use that? Or it has to be the immediate parent of it?
for instance, I have:
root >> mainframe >> entry. Does it have to be root, mainframe, or could be both?)

All usages of self are due to the use of classes. They have absolutely nothing to do with the validation. Nothing at all.
Here's an example without using classes, and without the long comment describing the validation function:
import Tkinter as tk
def OnValidate(d, i, P, s, S, v, V, W):
print "OnValidate:"
print "d='%s'" % d
print "i='%s'" % i
print "P='%s'" % P
print "s='%s'" % s
print "S='%s'" % S
print "v='%s'" % v
print "V='%s'" % V
print "W='%s'" % W
# only allow if the string is lowercase
return (S.lower() == S)
root = tk.Tk()
vcmd = (root.register(OnValidate),
'%d', '%i', '%P', '%s', '%S', '%v', '%V', '%W')
entry = tk.Entry(root, validate="key",
validatecommand=vcmd)
entry.pack()
root.mainloop()
Note: the point of registering a command is to create a bridge between the underlying tcl/tk engine and the python library. In essence it creates a tcl command that calls the OnValidate function, giving it the supplied arguments. This is necessary because tkinter failed to provide a suitable interface to the input validation features of tk. You don't need to do this step if you don't want all of the fancy variables (%d, %i, etc).
The error NameError: name 'entry' is not defined is because you are using entry before you define what entry is. One of the benefits of using classes is that it allows you define methods further down in the file than where you use them. By using a procedural style you are forced to define functions before they are used*.
* technically speaking, you always have to define functions before they are used. In the case of using classes, you don't actually use the methods of a class until after you've created the instance of the class. The instance isn't actually created until very near the end of the file, which lets you define the code well before you use it.

Related

Tkinter ValueError: could not convert string to float: ''

It's the whole day I'm trying to solve this ridiculous problem, but without success; in this forum there is a lot of material, but usually they are enormous amounts of code and I cannot understand what's going on. On the internet they generally suggest casting but it doesn't work. To make things easy I wrote a sample code to illustrate the issues I'm having in a bigger code
import tkinter as tk
from tkinter import *
#from tkinter import ttk
my_window = tk.Tk()
my_label = tk.Label(master=my_window,text="N")
my_label.pack()
my_entry = tk.Entry(master=my_window)
my_entry.pack()
N = my_entry.get()
print(float(N))
my_window.mainloop()
Very nice and simple, but I get the following message
ValueError: could not convert string to float: ''
I tried many possibilities, but anything worked. Any little suggestion is very much appreciated. Thanks
Edit:
I want to understand how to assign float(N) to a global variable, say a, so I can use it later on in the code, and I just took the code of #Cool Cloud and modified it a little
import tkinter as tk
from tkinter import *
my_window = tk.Tk()
a=1.
def cast():
N = my_entry.get()
try: # Try to execute this
print(float(N))
a=float(N)
except ValueError: # If ValueError(meaning it is not a float) is triggered then print...
print('Not a number!')
my_label = tk.Label(master=my_window,text="N")
my_label.pack()
my_entry = tk.Entry(master=my_window)
my_entry.pack()
# N = my_entry.get() N will be empty as my_entry is empty when code is executed
Button(my_window,text='Click me to convert to
float',command=cast).pack() # A button to trigger event
print(a)
my_window.mainloop()
The output of this is in the following image
As you can see it directly prints 1.0 without waiting for the assignment a=float(N), so my doubt is how can I actually do this assignment, to use it later in my code. Thanks
P.S.: I understand that print(a) inside the definition of cast() would give correctly 123.0 in this case, but my problem is more general: I'm trying to understand how to entry an N value, making it float and "propagate" it to the rest of the program. My doubt is given by the fact that print(a) almost at the very last line of the program, and still doesn't wait for cast() to come in.
GUI programming is event driven, which means you will have to code based on events triggered. Every python code runs from top to bottom, and in this case all the code from top to bottom, outside functions, is executed. That means as soon as:
my_entry = tk.Entry(master=my_window)
my_entry.pack()
...is executed, the next line to be executed is N = my_entry.get(), and at the time of execution there is NOTHING inside the entry widget and hence N becomes empty string, and then you are trying to convert that empty string to a float, which will obviously give an error.
What you should be doing is, make a button and when you click it(event is triggered) and connect it to a function that will get the input and convert to float. So that when you click the button you have entered something onto the entry and now it is no longer empty.
import tkinter as tk
from tkinter import *
my_window = tk.Tk()
def cast():
N = my_entry.get()
try: # Try to execute this
print(float(N))
except ValueError: # If ValueError(meaning it is not a float) is triggered then print...
print('Not a number!')
my_label = tk.Label(master=my_window,text="N")
my_label.pack()
my_entry = tk.Entry(master=my_window)
my_entry.pack()
# N = my_entry.get() N will be empty as my_entry is empty when code is executed
Button(my_window,text='Click me to convert to float',command=cast).pack() # A button to trigger event
my_window.mainloop()
Another way, and an unpopular way, is to use DoubleVar() which will get() the text for you, in this case, your entry will accept any value but when you get() the value from the DoubleVar() it will raise a TclError if it is not a float.
def cast():
# Unlike before, error will be generated at get() and you dont need float()
# because if there is no error then it is guaranteed to be a float anyway
try:
N = var.get()
print(N)
except TclError: # If TclError is triggered then print...
print('Not a number!')
var = DoubleVar() # tkinter control variable
my_entry = tk.Entry(master=my_window,textvariable=var)
my_entry.pack()
Note: You can do my_entry.get() and it wont give any error even if the input is not a float, error comes only in the case of var.get().
From:https://www.geeksforgeeks.org/python-tkinter-entry-widget/
"get() : Returns the entry’s current text as a string."
I think that you can't just convert string to float.

Struggling to understand this Tkinter entry validation code [duplicate]

What is the recommended technique for interactively validating content in a tkinter Entry widget?
I've read the posts about using validate=True and validatecommand=command, and it appears that these features are limited by the fact that they get cleared if the validatecommand command updates the Entry widget's value.
Given this behavior, should we bind on the KeyPress, Cut, and Paste events and monitor/update our Entry widget's value through these events? (And other related events that I might have missed?)
Or should we forget interactive validation altogether and only validate on FocusOut events?
The correct answer is, use the validatecommand attribute of the widget. Unfortunately this feature is severely under-documented in the Tkinter world, though it is quite sufficiently documented in the Tk world. Even though it's not documented well, it has everything you need to do validation without resorting to bindings or tracing variables, or modifying the widget from within the validation procedure.
The trick is to know that you can have Tkinter pass in special values to your validate command. These values give you all the information you need to know to decide on whether the data is valid or not: the value prior to the edit, the value after the edit if the edit is valid, and several other bits of information. To use these, though, you need to do a little voodoo to get this information passed to your validate command.
Note: it's important that the validation command returns either True or False. Anything else will cause the validation to be turned off for the widget.
Here's an example that only allows lowercase. It also prints the values of all of the special values for illustrative purposes. They aren't all necessary; you rarely need more than one or two.
import tkinter as tk # python 3.x
# import Tkinter as tk # python 2.x
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
# valid percent substitutions (from the Tk entry man page)
# note: you only have to register the ones you need; this
# example registers them all for illustrative purposes
#
# %d = Type of action (1=insert, 0=delete, -1 for others)
# %i = index of char string to be inserted/deleted, or -1
# %P = value of the entry if the edit is allowed
# %s = value of entry prior to editing
# %S = the text string being inserted or deleted, if any
# %v = the type of validation that is currently set
# %V = the type of validation that triggered the callback
# (key, focusin, focusout, forced)
# %W = the tk name of the widget
vcmd = (self.register(self.onValidate),
'%d', '%i', '%P', '%s', '%S', '%v', '%V', '%W')
self.entry = tk.Entry(self, validate="key", validatecommand=vcmd)
self.text = tk.Text(self, height=10, width=40)
self.entry.pack(side="top", fill="x")
self.text.pack(side="bottom", fill="both", expand=True)
def onValidate(self, d, i, P, s, S, v, V, W):
self.text.delete("1.0", "end")
self.text.insert("end","OnValidate:\n")
self.text.insert("end","d='%s'\n" % d)
self.text.insert("end","i='%s'\n" % i)
self.text.insert("end","P='%s'\n" % P)
self.text.insert("end","s='%s'\n" % s)
self.text.insert("end","S='%s'\n" % S)
self.text.insert("end","v='%s'\n" % v)
self.text.insert("end","V='%s'\n" % V)
self.text.insert("end","W='%s'\n" % W)
# Disallow anything but lowercase letters
if S == S.lower():
return True
else:
self.bell()
return False
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
For more information about what happens under the hood when you call the register method, see Why is calling register() required for tkinter input validation?
For the canonical documentation see the Validation section of the Tcl/Tk Entry man page
After studying and experimenting with Bryan's code, I produced a minimal version of input validation. The following code will put up an Entry box and only accept numeric digits.
from tkinter import *
root = Tk()
def testVal(inStr,acttyp):
if acttyp == '1': #insert
if not inStr.isdigit():
return False
return True
entry = Entry(root, validate="key")
entry['validatecommand'] = (entry.register(testVal),'%P','%d')
entry.pack()
root.mainloop()
Perhaps I should add that I am still learning Python and I will gladly accept any and all comments/suggestions.
Use a Tkinter.StringVar to track the value of the Entry widget. You can validate the value of the StringVar by setting a trace on it.
Here's a short working program that accepts only valid floats in the Entry widget.
try:
from tkinter import *
except ImportError:
from Tkinter import * # Python 2
root = Tk()
sv = StringVar()
def validate_float(var):
new_value = var.get()
try:
new_value == '' or float(new_value)
validate_float.old_value = new_value
except:
var.set(validate_float.old_value)
validate_float.old_value = '' # Define function attribute.
# trace wants a callback with nearly useless parameters, fixing with lambda.
sv.trace('w', lambda nm, idx, mode, var=sv: validate_float(var))
ent = Entry(root, textvariable=sv)
ent.pack()
ent.focus_set()
root.mainloop()
Bryan's answer is correct, however no one mentioned the 'invalidcommand' attribute of the tkinter widget.
A good explanation is here:
http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/entry-validation.html
Text copy/pasted in case of broken link
The Entry widget also supports an invalidcommand option that specifies a callback function that is called whenever the validatecommand returns False. This command may modify the text in the widget by using the .set() method on the widget's associated textvariable. Setting up this option works the same as setting up the validatecommand. You must use the .register() method to wrap your Python function; this method returns the name of the wrapped function as a string. Then you will pass as the value of the invalidcommand option either that string, or as the first element of a tuple containing substitution codes.
Note:
There is only one thing that I cannot figure out how to do: If you add validation to an entry, and the user selects a portion of the text and types a new value, there is no way to capture the original value and reset the entry. Here's an example
Entry is designed to only accept integers by implementing 'validatecommand'
User enters 1234567
User selects '345' and presses 'j'. This is registered as two actions: deletion of '345', and insertion of 'j'. Tkinter ignores the deletion and acts only on the insertion of 'j'. 'validatecommand' returns False, and the values passed to the 'invalidcommand' function are as follows: %d=1, %i=2, %P=12j67, %s=1267, %S=j
If the code does not implement an 'invalidcommand' function, the 'validatecommand' function will reject the 'j' and the result will be 1267. If the code does implement an 'invalidcommand' function, there is no way to recover the original 1234567.
Define a function returning a boolean that indicates whether the input is valid.Register it as a Tcl callback, and pass the callback name to the widget as a validatecommand.
For example:
import tkinter as tk
def validator(P):
"""Validates the input.
Args:
P (int): the value the text would have after the change.
Returns:
bool: True if the input is digit-only or empty, and False otherwise.
"""
return P.isdigit() or P == ""
root = tk.Tk()
entry = tk.Entry(root)
entry.configure(
validate="key",
validatecommand=(
root.register(validator),
"%P",
),
)
entry.grid()
root.mainloop()
Reference.
While studying Bryan Oakley's answer, something told me that a far more general solution could be developed. The following example introduces a mode enumeration, a type dictionary, and a setup function for validation purposes. See line 48 for example usage and a demonstration of its simplicity.
#! /usr/bin/env python3
# https://stackoverflow.com/questions/4140437
import enum
import inspect
import tkinter
from tkinter.constants import *
Mode = enum.Enum('Mode', 'none key focus focusin focusout all')
CAST = dict(d=int, i=int, P=str, s=str, S=str,
v=Mode.__getitem__, V=Mode.__getitem__, W=str)
def on_validate(widget, mode, validator):
# http://www.tcl.tk/man/tcl/TkCmd/ttk_entry.htm#M39
if mode not in Mode:
raise ValueError('mode not recognized')
parameters = inspect.signature(validator).parameters
if not set(parameters).issubset(CAST):
raise ValueError('validator arguments not recognized')
casts = tuple(map(CAST.__getitem__, parameters))
widget.configure(validate=mode.name, validatecommand=[widget.register(
lambda *args: bool(validator(*(cast(arg) for cast, arg in zip(
casts, args)))))]+['%' + parameter for parameter in parameters])
class Example(tkinter.Frame):
#classmethod
def main(cls):
tkinter.NoDefaultRoot()
root = tkinter.Tk()
root.title('Validation Example')
cls(root).grid(sticky=NSEW)
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
root.mainloop()
def __init__(self, master, **kw):
super().__init__(master, **kw)
self.entry = tkinter.Entry(self)
self.text = tkinter.Text(self, height=15, width=50,
wrap=WORD, state=DISABLED)
self.entry.grid(row=0, column=0, sticky=NSEW)
self.text.grid(row=1, column=0, sticky=NSEW)
self.grid_rowconfigure(1, weight=1)
self.grid_columnconfigure(0, weight=1)
on_validate(self.entry, Mode.key, self.validator)
def validator(self, d, i, P, s, S, v, V, W):
self.text['state'] = NORMAL
self.text.delete(1.0, END)
self.text.insert(END, 'd = {!r}\ni = {!r}\nP = {!r}\ns = {!r}\n'
'S = {!r}\nv = {!r}\nV = {!r}\nW = {!r}'
.format(d, i, P, s, S, v, V, W))
self.text['state'] = DISABLED
return not S.isupper()
if __name__ == '__main__':
Example.main()
import tkinter
tk=tkinter.Tk()
def only_numeric_input(e):
#this is allowing all numeric input
if e.isdigit():
return True
#this will allow backspace to work
elif e=="":
return True
else:
return False
#this will make the entry widget on root window
e1=tkinter.Entry(tk)
#arranging entry widget on screen
e1.grid(row=0,column=0)
c=tk.register(only_numeric_input)
e1.configure(validate="key",validatecommand=(c,'%P'))
tk.mainloop()
#very usefull for making app like calci
Here's an improved version of #Steven Rumbalski's answer of validating the Entry widgets value by tracing changes to a StringVar — which I have already debugged and improved to some degree by editing it in place.
The version below puts everything into a StringVar subclass to encapsulates what's going on better and, more importantly allow multiple independent instances of it to exist at the same time without interfering with each other — a potential problem with his implementation because it utilizes function attributes instead of instance attributes, which are essentially the same thing as global variables and can lead to problems in such a scenario.
try:
from tkinter import *
except ImportError:
from Tkinter import * # Python 2
class ValidateFloatVar(StringVar):
"""StringVar subclass that only allows valid float values to be put in it."""
def __init__(self, master=None, value=None, name=None):
StringVar.__init__(self, master, value, name)
self._old_value = self.get()
self.trace('w', self._validate)
def _validate(self, *_):
new_value = self.get()
try:
new_value == '' or float(new_value)
self._old_value = new_value
except ValueError:
StringVar.set(self, self._old_value)
root = Tk()
ent = Entry(root, textvariable=ValidateFloatVar(value=42.0))
ent.pack()
ent.focus_set()
ent.icursor(END)
root.mainloop()
This code can help if you want to set both just digits and max characters.
from tkinter import *
root = Tk()
def validate(P):
if len(P) == 0 or len(P) <= 10 and P.isdigit(): # 10 characters
return True
else:
return False
ent = Entry(root, validate="key", validatecommand=(root.register(validate), '%P'))
ent.pack()
root.mainloop()
Responding to orionrobert's problem of dealing with simple validation upon substitutions of text through selection, instead of separate deletions or insertions:
A substitution of selected text is processed as a deletion followed by an insertion. This may lead to problems, for example, when the deletion should move the cursor to the left, while a substitution should move the cursor to the right. Fortunately, these two processes are executed immediately after one another.
Hence, we can differentiate between a deletion by itself and a deletion directly followed by an insertion due to a substitution because the latter has does not change the idle flag between deletion and insertion.
This is exploited using a substitutionFlag and a Widget.after_idle().
after_idle() executes the lambda-function at the end of the event queue:
class ValidatedEntry(Entry):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.tclValidate = (self.register(self.validate), '%d', '%i', '%P', '%s', '%S', '%v', '%V', '%W')
# attach the registered validation function to this spinbox
self.config(validate = "all", validatecommand = self.tclValidate)
def validate(self, type, index, result, prior, indelText, currentValidationMode, reason, widgetName):
if typeOfAction == "0":
# set a flag that can be checked by the insertion validation for being part of the substitution
self.substitutionFlag = True
# store desired data
self.priorBeforeDeletion = prior
self.indexBeforeDeletion = index
# reset the flag after idle
self.after_idle(lambda: setattr(self, "substitutionFlag", False))
# normal deletion validation
pass
elif typeOfAction == "1":
# if this is a substitution, everything is shifted left by a deletion, so undo this by using the previous prior
if self.substitutionFlag:
# restore desired data to what it was during validation of the deletion
prior = self.priorBeforeDeletion
index = self.indexBeforeDeletion
# optional (often not required) additional behavior upon substitution
pass
else:
# normal insertion validation
pass
return True
Of course, after a substitution, while validating the deletion part, one still won’t know whether an insert will follow.
Luckily however, with:
.set(),
.icursor(),
.index(SEL_FIRST),
.index(SEL_LAST),
.index(INSERT),
we can achieve most desired behavior retrospectively (since the combination of our new substitutionFlag with an insertion is a new unique and final event.

Passing StringVar object from Entry to Label within functions in tkinter

Hi I've been struggling to get this to work, each time i change something I receive another error. I've been trying to create an entry box with a function and then get the variable from the entry box into a label, created by a button press. When I tried to do this often this error came up.
TypeError: get() missing 1 required positional argument: 'self'
I then put self in in the method brackets.
command = lambda: x.myFunc(self.my_variable.get(self))
Then another error, which I'm not sure how to sort out.
AttributeError: 'My_Class' object has no attribute '_tk'
Here's the full code, I'm new to classes and self, so any corrections are welcome.
from tkinter import *
import time
class My_Class:
def start(self):
self.root=Tk()
self.my_variable=StringVar
self.entry_box=Entry(self.root, textvariable=self.my_variable)
self.entry_box.pack()
self.button=Button(self.root,text="Pass variable now",
command=lambda:x.myFunc(self.my_variable.get(self)))
self.button.pack()
def myFunc(self,my_variable):
self.lab=Label(self.root,text=self.my_variable)
self.lab.pack()
x=My_Class()
x.start()
This is the correct way to create a StringVar object:
text = StringVar() # note additional ()
Can you explain me what x is in the following statement:
lambda: x.myFunc(self.my_variable.get(self))
x is not visible inside the class, because it's declared outside the class.
myFunc is not indented correctly: you should indent it like the __init__ method.
I really recommend you to watch some tutorials on OOP before proceeding. You are basically trying to guess how OOP works.
If you make myFunc A method if the class (which you might be trying to do; it's hard to know because your indentation is wrong), you don't have to pass anything to myFunc. That function has access to everything in the class, so it can get what it needs, when it needs it. That lets you eliminate the use of lambda, which helps reduce complexity.
Also, you normally don't need a StringVar at all, it's just one more thing to keep track of. However, if you really need the label and entry to show exactly the same data, have them share the same textvariable and the text is updated automatically without you having to call a function, or get the value from the widget, or set the value n the label.
Here's an example without using StringVar:
class My_Class:
def start(self):
...
self.entry_box = Entry(self.root)
self.button = Button(..., command = self.myFunc)
...
def myFunc(self):
s = self.entry_box.get()
self.lab = Label(..., text = s)
...

Another Name error for my text adventure game Python

So i am making a text adventure game, and currently i am making the enemies. My class random_enemies makes trash mobs for your character to fight and i have a function in it called weak, normal, strong, etc... that scales with your character depending on which one it is. When i call random_enemies.weak it says (Name Error: global variable "p" is not defined) even though it should be.
import random
from character import *
from player import *
class random_enemies(character):
def __init__(self,name,hp,maxhp,attack_damage,ability_power,exp):
super(random_enemies,self).__init__(name,hp,maxhp)
self.attack_damage = attack_damage
self.ability_power = ability_power
self.exp = exp
def weak():
self.hp = random.randint(p.maxhp/10, p.maxhp/5)
self.attack_damage = None
self.ability_power = None
self.exp = None
from character import*
class player(character):
def __init__(self,name,hp,maxhp,attack_damage,ability_power):
super(player,self).__init__(name, hp, maxhp)
self.attack_damage = attack_damage
self.ability_power = ability_power
This is my player class and below is the class that player gets "maxhp" from.
class character(object):
def __init__(self,name,hp,maxhp):
self.name = name
self.hp = hp
self.maxhp = maxhp
def attack(self,other):
pass
p=player(Players_name, 100, 100, 10, 5,)
while (p.hp>0):
a=input("What do you want to do?")
if a=="Instructions":
Instructions()
elif a=="Commands":
Commands()
elif a=="Fight":
print("Level",wave,"Wave Begins")
if wave < 6:
b = random_enemies.weak()
print("A",b,"Appeared!")
print("Stats of",b, ": \n Health=", b.hp,"Attack Damage=",b.attack_damage)
continue
I just made this really quickly just to test if everything I had was working until I got the error. This is also the place where random_enemies.weak() was called. Also in this is where I defined what "p" was.
So, first of all, follow a naming convention. For python code I recommend that you use pep8 as a convention.
You have a problem with classes vs. instances in your code. First, you need an instance of a class before you can use it:
enemy = random_enemy() # a better name would be RandomEnemy
In Python, all methods start with self, and you need to pass to the method the arguments that it needs to do its work. weak is a method, so it should be more like this:
def weak(self, player):
# the method for weak ... weak attack ?
# remember to change p to player, which is more meaningful
...
Now that you have your instance and it has a method weak which receives a player as argument, you can use it as follows:
# you can't use random_enemy here as you tried because it is a class
# you need a random_enemy instance, the enemy that I talked about earlier
b = enemy.weak(player) # renamed p to player because it is more meaningful
For this all to work, you will need one more thing. weak() needs to return something. Right now you are using what it returns, nothing! The code that you posted is b = random_enemies.weak(). Because weak() does not have a return clause, b will always be None.
Some notes: Avoid one-letter variables unless there is a long standing convention (like using i for loop counter). It is easier to understand what you are trying to do if you define player instead of just p.
Python has a really great tutorial for all this stuff.

get rid of global variable in python3

First of all I'm using python 3.3 & 3.2 on windows and Linux respectively.
I am starting to build an rpn calculator. It looks like cross-platform key listeners is a kind of holy grail for python. So far this seems to be doing the trick, but I've created other problems:
I cannot get away from the global variable for entries and using my
stack.
It looks like I have to build the program from inside
callback()
Here is a rough skeleton that shows my direction. Am I missing a way to pass information in and out of callback()
The goal was to build an RPN class before i found myself stuck inside callback().
import tkinter as tk
entry = ""
stack = list()
operators = {"+",
"-",
"*",
"/",
"^",
"sin",
"cos",
"tan"}
def operate(_op):
if _op == "+":
print("plus")
def callback(event):
global entry, stack
entry = entry + event.char
if event.keysym == 'Escape': # exit program
root.destroy()
elif event.keysym=='Return': # push string onto stack TODO
print(entry)
entry = ""
elif entry in operators:
operate(entry)
root = tk.Tk()
root.withdraw()
root.bind('<Key>', callback)
root.mainloop()
You have several options to do what you want to do.
1. Use a Class for your application
The canonical way of doing what you wish without resorting to a global variable is to place the application within a class, and pass a method as a callback (see print_contents) the following is straight from the docs:
class App(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.pack()
self.entrythingy = Entry()
self.entrythingy.pack()
# here is the application variable
self.contents = StringVar()
# set it to some value
self.contents.set("this is a variable")
# tell the entry widget to watch this variable
self.entrythingy["textvariable"] = self.contents
# and here we get a callback when the user hits return.
# we will have the program print out the value of the
# application variable when the user hits return
self.entrythingy.bind('<Key-Return>',
self.print_contents)
def print_contents(self, event):
print("hi. contents of entry is now ---->",
self.contents.get())
2. Curry the callback over your state
You can also use Python's functional programming constructs to curry a function over a global variable and then pass the curried function as a callback.
import functools
global_var = {}
def callback(var, event):
pass
#...
root.bind('<Key>', functools.partial(callback, global_var))
Although this probably isn't what you want.
3. Use a global variable
Sometimes, a global variable is ok.
4. Re-architect for cleanliness and readability
However, you most definitely do not have to build your program inside the callback.
In fact, I would recommend that you create a suite of test with various valid and invalid input, and make a Calculator class or function that takes a string input of RPN commands and returns a value. This is easy to test without a tkinter interface, and will be far more robust.
Then, use your callback to build a string which you pass to your Calculator.
If you want incremental calculation (ie, you're building a simulator), then simply make your Calculator accept single tokens rather than entire equations, but the design remains similar. All the state is then encapsulated inside the Calculator rather than globally.

Resources