As I'm new in python maybe I'm missing the obvious.
I've wrote the updateRates method for the class which counts fps in this RDP program.
When I try to access some attributes from inside the call from the timer method there is this weird Error:
"AttributeError: type object 'RDCToGUI' has no attribute '_cnt_framerate'"
it is really obvious this class inherits this attribute, but in the Timer thread it doesn't seem to be there anymore
So the problem is: I can call the inherited methods of the super (e.g. self.rates) but inside some attributes aren't present in the method
class RDCToGUI(clientProtocol.rdc):
def __init__(self):
clientProtocol.rdc.__init__(self)
self.num = 0
self.count = 0
self.framerate_before = 0
def updateRates(self, framerate_label, datarate_label):
divisor = 1
datarate_text = "b/s"
self.rates(self)
framerate = self.framerate
datarate = self.datarate
if self.logged_in == 1 and self.framerate_before == 0 == framerate:
self.framebufferUpdateRequest(
width=800, height=800)
if datarate > 1000000:
divisor = 1000000
rateText = "Mb/s"
elif datarate > 1000:
divisor = 1000
rateText = "Kb/s"
self.framerate_before = framerate
framerate_label.setText(f"Framerate: {framerate}")
datarate_label.setText(
f"Datarate: {round(datarate / divisor, 2)} {datarate_text}")
threading.Timer(1, self.updateRates, args=(self,
framerate_label, datarate_label)).start()
class rdc(Protocol):
def __init__(self):
self._packet = ""
self._expected_len = 0
self._cnt_framerate = 0
self._cnt_datarate = 0
self.framerate = 0
self.datarate = 0
self.logged_in = 0
def rates(self):
self.setRates(self)
self.resetCounter(self)
def setRates(self):
self.framerate = self._cnt_framerate
self.datarate = self._cnt_datarate
def resetCounter(self):
self._cnt_datarate = 0
self._cnt_framerate = 0
def incFramerate(self):
self._cnt_framerate += 1
def addDataSize(self, size):
self._cnt_datarate += size
The classes have more methods and members than what I showed, but wanted to cut it to the most important stuff.
Tried to put the method in different classes and played around with the names of the attributes
class rdc is in another file which is imported as clientProtocol.
This seems like an inheritance issue. Maybe try replacing
clientProtocol.rdc.__init__(self)
with
super(RDCToGUI, self).__init__()
I'm not an expert on inheritance either and haven't really used it, but there is a ton to read on the super() function and you could maybe start at Understanding Python super() with __init__() methods and just follow it down the rabbit hole :)
Related
I have a problem related to a TKinter GUI I am creating, but the problem is not necessarily specific to this library.
Background
I am currently in the advanced stage of a python self-learning course. The learning module I am on is covering TKinter for creating interactive GUI's. I am making a game whereby randomly generated numbered buttons must be clicked in succession in the quickest time possible.
Brief: https://edube.org/learn/pcpp1-4-gui-programming/lab-the-clicker
Problem
Under my class, game_grid, I have created an instance variable; 'self.holder', a 25 entry dictionary of {Key : TkinterButtonObject} form
When calling this instance variable for use in a class method, I get the following error:
AttributeError: 'game_grid' object has no attribute 'holder'
I have a print statement under class init which proves this attribute has been successfully created. I have made sure my spacing and tabs are all OK, and have tried every location for this variable, including using as a class variable, and a global variable to no avail - as it is an semi-complex object. I don't see what difference it should make, but any ideas would be much appreciated. I am also aware this could be done without classes, but I am trying to adopt DRY principles and orthogonality in all of my programs.
Thanks in advance.
Full Code:
import tkinter as tk
from tkinter import*
import random
from tkinter import messagebox
import time
win = tk.Tk()
class game_grid:
def __init__(self, win):
self.last_number = 0
self.number_buttons = {}
self.row_count = 0
self.column_count = 0
#Generate a list of 25 random numbers
self.number_list = random.sample(range(0, 999), 25)
#Puts the numbers in a dictionary (number : buttonobject)
self.holder = {i: tk.Button(win, text = str(i), command = game_grid.select_button(self, i)) for i in self.number_list}
#pack each object into window by iterating rows and columns
for key in self.holder:
self.holder[key].grid(column = self.column_count, row = self.row_count)
if self.column_count < 4:
self.column_count += 1
elif self.column_count == 4:
self.column_count = 0
self.row_count += 1
print(self.holder)
def select_button(self, number):
if number > self.last_number:
self.holder[number].config(state=tk.DISABLED)
self.last_number = number
else:
pass
class stopclock():
def __init__(self):
#Stopclock variable initialisation
self.time_begin = 0
self.time_end = 0
self.time_elapsed= 0
def start(self):
if self.time_begin == 0:
self.time_begin = time.time()
return("Timer started\nStart time: ", self.time_begin)
else:
return("Timer already active")
def stop(self):
self.time_end = time.time()
self.time_elapsed = time_end - time_begin
return("Timer finished\nEnd time: ", time_begin,"\nTime Elapsed: ", time_elapsed)
play1 = game_grid(win)
win.mainloop()
Perhaps you meant:
command = self.select_button(self, i)
Update:
Though from research:How to pass arguments to a Button command in Tkinter?
It should be:
command = lambda i=i: self.select_button(i)
You call select_button from inside the dict comprehension of holder. select_button then tries to use holder, but it is not yet defined. You don't want to actually call select_button, but assign a function to the button, like that:
self.holder = {i: tk.Button(window, text=str(i), command=lambda i=i: self.select_button(i)) for i in self.number_list}
I have this class in Python. (Removed the unecessary parts)
class Spillebrett:
def __init__(self, rader, kolonner):
self._rader = rader
self._kolonner = kolonner
self._generasjonsnummer = 0
I need to add 1 to _generasjonsnummer everytime i run the class. If i try _generasjonsnummer += 1 i get an error. I tried to make a def addGenerasjonsnummer() and call it in the init like so:
class Spillebrett:
def __init__(self, rader, kolonner):
self._rader = rader
self._kolonner = kolonner
self._generasjonsnummer = 0
addGenerasjonsnummer()
def addGenerasjonsnummer():
self._generasjonsnummer += 1
But i cant call on functions in the init. What i need is for this number to update to +=1 each time i start the init, how do i do this?
If your class has an attribute that is going to be incremented each time the constructor is called, then you should disassociate it with any instance to begin with. In other words, declare it as a class attribute, outside __init__.
I'd recommend something along these lines:
In [625]: class Spillebrett:
...: ctr = 0
...: def __init__(self):
...: print(Spillebrett.ctr)
...: Spillebrett.ctr += 1
...:
In [626]: Spillebrett()
0
Out[626]: <__main__.Spillebrett at 0x10b918da0>
In [627]: Spillebrett()
1
Out[627]: <__main__.Spillebrett at 0x109f1af98>
In [628]: Spillebrett()
2
Out[628]: <__main__.Spillebrett at 0x10b918470>
I'm developing an application that reads a message input from telegram with a set of variables, and then starts a game with the user. So I created a class that represents an instance of the game, making one game per chat possible:
class Battle:
def __init__(self, mainchat):
self.mainchat = mainchat
print('Instance of battle started on chat %s' % self.mainchat)
pcount = 0
team1 = []
team2 = []
p1 = ()
p2 = ()
p1score = 0
p2score = 0
battlechoicep1 = -1
battlechoicep2 = -1
so, as soon as I get a message, I start an instance of a battle based on user inputes, e.g.
battle = Battle(chat_id)
battle.p1 = 'Paul'
battle.battlechoicep1 = 4
...
this way has been working fine right now, but every time I want to reset the battle, I go through a function that does this:
battle.pcount = 0
battle.team1 = []
battle.team2 = []
battle.p1 = ()
battle.p2 = ()
battle.p1score = 0
battle.p2score = 0
battle.battlechoicep1 = -1
battle.battlechoicep2 = -1
save() # outside function that saves the scores into a pickle file
return
So, I would like to make it so this is a function inside my class, so everytime I call battle.reset it would call something like this
def reset():
battle.pcount = 0
battle.team1 = []
battle.team2 = []
battle.p1 = ()
battle.p2 = ()
battle.p1score = 0
battle.p2score = 0
battle.battlechoicep1 = -1
battle.battlechoicep2 = -1
save() # outside function that saves the scores into a pickle file
return
I don't know how is the right approach to this problem, I don't even know if what I've been doing up to now is 'correct' (it is working at least).
Creating the function inside the class (like def reset(self):) seems to have no effect.
You're on the right track with def reset(self). You just need to change the instances of battle to self in the method itself. NOTE: This needs to be a method of the Battle class.
def reset(self):
self.pcount = 0
... # etc
save() # outside function that saves the scores into a pickle file
When you pass in self as the first parameter of a class method, it allows the method to work on the instance of the class that you've called it on. If you just do def reset(self) without changing the battle to self, it will try to modify a variable in the current scope called battle, which in this case probably doesn't exist.
The other thing you could do if you just want reset to create a completely new object without preserving any of the attributes, you can just do:
def reset(self):
return Battle()
You're almost there!
class Battle:
def __init__(self, mainchat):
self.mainchat = mainchat
print('Instance of battle started on chat %s' % self.mainchat)
self.reset()
def reset(self):
self.team1, self.team2 = [], []
self.p1 = self.p2 = () #New tuples will be assigned and overwritten
self.pcount = self.p1score = self.p2score = 0
self.battlechoicep1 = self.battlechoicep2 = -1
save() # outside function that saves the scores into a pickle file
So when you need to reset, just call battle.reset()! Maybe the save function can also be a class method as well, just follow the same format.
I'm trying to understand when you would want to have an instance of a class, and what exactly the difference is between these two variations of code:
Class A takes a time and and assigns it to a new variable, and then returns that new variable.
class A:
def B(time):
seconds = time
return seconds
seconds = A.B(int)
Class C takes a time in as well, but also creates an instance of function D (using self) and then returns self.seconds.
class C:
def D(self, time):
self.seconds = time
return self.seconds
seconds = C().D(int)
They end up returning the same values. I'm have difficulty understanding how these two pieces of code are different. Is one superior in certain situations vs. the other?
Thank you!
EDIT: Added calls to both functions.
Perhaps the following (very simplified) example helps to see where it can be useful to have an instance of a class:
class A:
minutes = 2
def time(seconds):
return 60*A.minutes + seconds
class B:
minutes = 2
def time(self, seconds):
return 60*self.minutes + seconds
print("Class:")
print(A.time(30))
a1 = A
a2 = A
a1.minutes = 3
a2.minutes = 4
print(a1.time(30)) # 60*4 + 30
print(a2.time(30)) # 60*4 + 30
print("Instance:")
print(B().time(30))
b1 = B()
b2 = B()
b1.minutes = 3
b2.minutes = 4
print(b1.time(30)) # 60*3 + 30
print(b2.time(30)) # 60*4 + 30
This results in:
Class:
150
270
270
Instance:
150
210
270
At the core, what you are asking for is understanding the difference between a Static Method and a normal Method, and what the advantages of OOP are.
A Static method can be called without instantiating the class, such as in your first example. This allows you to group related functions under a single header (the class), but it is not, technically, OOP.
However, a Static method has no instance data to act upon, as there is no instance.
A class instance allows you to keep track of Object specific data, and act upon them within your methods.
For example:
import time
class Stopwatch:
def __init__(self, name, start):
self.name = name
self.time = start
self.active = False
def start(self):
self.active = True
def stop(self):
self.active = False
def reset(self, time):
self.time = time
def isComplete(self):
return (self.time == 0)
def tick(self):
if self.active:
self.time -= 1
print(self.name+": ",self.time)
if self.time == 0:
print("Timer of " + self.name + " has reached zero!")
self.stop()
watchA = Stopwatch("A", 10)
watchB = Stopwatch("B", 5)
watchA.start()
watchB.start()
while (not watchA.isComplete()) or (not watchB.isComplete()):
time.sleep(1)
watchA.tick()
watchB.tick()
Running this outputs:
A: 9
B: 4
A: 8
B: 3
A: 7
B: 2
A: 6
B: 1
A: 5
B: 0
Timer of B has reached zero!
A: 4
A: 3
A: 2
A: 1
A: 0
Timer of A has reached zero!
Notice how the time of each watch is tracked separately, despite using the same code. This would not be possible using Static methods, as the data is attached not to the Class, but to the Instanced objects themselves.
For some reason my while loop is stopping after two tries and I can't figure out what's wrong...
It's supposed to be an ant farm, where you can choose to breed and make a new ant, etc.
I just don't understand why it's stopping...
Here's my code:
import random
class Colony(object):
workerAnts = 0
list = []
temp = []
foodAmount = 10
def breedWorker(self):
if Colony.foodAmount < 5:
print "Sorry! You do not have enough food to create a new worker ant!"
else:
Colony.foodAmount -= 5
Colony.workerAnts += 1
Colony.list.append("ant")
def step(self):
number = 'ant'
for number in Colony.list:
a = Ant()
a.forage()
if Colony.foodAmount > 0:
Colony.foodAmount -= 1
if Colony.foodAmount < len(Colony.list):
for number in Colony.list[Colony.foodAmount+1:]:
Ant.health -= 1
def purge(self):
number = 'ant'
for number in Colony.list:
if Ant.health > 0:
Colony.temp.append("ant")
Colony.list = Colony.temp
class Ant(object):
health = 10
def forage(self):
if Ant.health == 0:
Colony.workerAnts -= 1
if random.randint(0,100) > 95:
Ant.health = 0
print "Ant has died from a horrible accident!"
Colony.workerAnts -= 1
elif random.randint(0,100) < 40:
newFood = random.randint(1,5)
print "Ant has found %s food!!" % newFood
Colony.foodAmount += newFood
elif random.randint(0,100) < 5:
Ant.health = 10
Colony.foodAmount += 10
print "You've found sweet nectar! Your ant has returned to full health and has brought 10 food back to the colony!"
else:
print "Ant returned empty-handed!"
def main():
queen = Colony()
queen2 = Ant()
while queen.workerAnts > 0 or queen.foodAmount >= 5:
print "========================================================"
print """
Your colony has %s ants and %s food, Your Majesty.\nWhat would you like to do?\n0: Do nothing.\n1: Breed worker. (Costs 5 food.)""" % (queen.workerAnts, queen.foodAmount)
answer = int(raw_input(">"))
if answer != 1 and answer != 0:
print "Sorry, invalid input!"
if answer == 0:
queen.step()
queen.purge()
if answer == 1:
print "Breeding Worker..."
queen.breedWorker()
queen.step()
queen.purge()
if queen.workerAnts <= 0 and queen.foodAmount < 5:
print "I'm sorry! Your colony has died out!"
You don't have constructors (__init__(self, ...)) and do not initialize object's properties
in methods you call this object property by self.property, not by Classname.property; in python you explicitly pass instance or class object to method, by convention they should be 'self' for instance, or 'cls' for class.
If you want use any Colony properties in Ant object or vice versa, you need to explicitly pass the reference, and store it as property. The most sensible would be to create Ant from Colony by calling something ants.append(Ant(self)); Ant's constructor should have signature `def init(self, colony):'
Well, that's because of the following line in def purge(self):
Colony.list = Colony.temp
The first time purge() is ran, it makes both Colony.list and Colony.temp point to the same array in memory. So the second time you run purge(), you go into an infinite loop, where you for number in Colony.list: do Colony.temp.append("ant"), which actually increases Colony.list as well, and the loop never exits, since it will always have a new member.
In python, for loops create iterators for the given object (if it's not already an iterator). In every iteration, python will call the iterator's next() method (in this case - the list). If next() can't yield a new value to iterate over, it raises StopIteration, and the loop exits. Don't worry, this exception is automatically handled for you by the for statement. In your case, Colony.list.next() always finds a new value (since you've just appended to it), and will never reach the end.
To fix your code, try slicing. This means that the array is copied, instead of pointing the two names to the same array:
Colony.list = Colony.temp[:]
You made Ant.health a class variable (shared between all Ant instances).
As soon as one ant's health goes to 0, they all die.
Here is an improved version. The following code is Python 2 and 3 compatible, and I think fixes all the errors!
import random
import sys
if sys.hexversion < 0x3000000:
# Python 2.x
inp = raw_input
rng = xrange
else:
# Python 3.x
inp = input
rng = range
def get_int(prompt, lo=None, hi=None):
"""
Prompt until an integer value in [lo..hi] is entered, then return it
"""
while True:
try:
val = int(inp(prompt))
if (lo is None or lo <= val) and (hi is None or val <= hi):
return val
except ValueError:
pass
class InsufficientFoodError(Exception):
pass
class Colony:
def __init__(self, workers=0, food=10):
self.food = food + Ant.cost * workers
self.ants = []
for i in rng(workers):
self.add_ant()
def add_ant(self):
try:
self.ants.append(Ant(self))
except InsufficientFoodError as e:
print(e)
def step(self):
# all ants eat, then all ants forage:
for ant in self.ants:
ant.eat()
for ant in self.ants:
ant.forage()
# bring out yer dead!
self.ants = [ant for ant in self.ants if ant.is_alive()]
def add_food(self, amount):
self.food += amount
def take_food(self, amount):
amt = min(amount, self.food)
self.food -= amt
return amt
def num_ants(self):
return len(self.ants)
class Ant:
cost = 5
max_health = 10
def __init__(self, colony):
# try to get enough food to produce an ant
food = colony.take_food(Ant.cost)
if food < Ant.cost:
# Failed! return any taken food and throw an error
colony.add_food(food)
raise InsufficientFoodError('The colony does not have enough food to make a new Ant')
else:
# Success!
self.colony = colony
self.health = Ant.max_health
def eat(self):
if self.health > 0:
self.health -= 1 - self.colony.take_food(1)
if self.health == 0:
print("An ant starved to death.")
def forage(self):
if self.is_alive():
dice = random.randint(0, 100)
if dice <= 5:
self.health = Ant.max_health
self.colony.add_food(10)
print("You've found sweet nectar! Your ant has returned to full health and has brought 10 food back to the colony!")
elif dice <= 40:
found_food = random.randint(1, 5)
self.colony.add_food(found_food)
print("Ant has found {} food!".format(found_food))
elif dice <= 95:
print("Ant returned empty-handed!")
else:
self.health = 0
print("Ant has died from a horrible accident!")
def is_alive(self):
return self.health > 0
def main():
colony = Colony()
while True:
print(
"========================================================\n"
"\n"
"Your colony has {ants} ants and {food} food, Your Majesty.\n"
"What would you like to do?\n"
" 1: Do nothing\n"
" 2: Breed worker (costs {cost} food)"
.format(ants=colony.num_ants(), cost=Ant.cost, food=colony.food)
)
opt = get_int("> ", 1, 2)
if opt == 2:
print("Breeding Worker...")
colony.add_ant()
colony.step()
if colony.num_ants() == 0 and colony.food < Ant.cost:
print("I'm sorry! Your colony has died out!")
break
if __name__=="__main__":
main()
This answer is a little off, but seems like it will be a valuable piece of knowledge.
A big issue here is that your classes are being used in an undesirable way.
The main advantage of a class is to hold an instance of variables/functions, such that you can have many independent groupings of them.
By calling Colony.<var> you're changing the var of the base( or super) class variable. This works if you only want to have one Colony,... but what if you want two,. or three! or a million!!?
Notice how you get an error when you don't enter self as the first parameter to your class functions? What you need to realize is that you're passing an instance of your class as the first parameter. This is how the class knows what grouping of variables to use.
say we have a class Antzilla
class Antzilla:
antvar = "antzilla var"
def PrintSuperAntvar(self):
print Antzilla.antvar
def PrintInstanceOfAntvar(self):
print self.antvar
notice that PrintSuperAntvar calls the base var and that PrintInstanceOfAntvar prints an instance of Antzilla
If I make az1 and change az1.antvar it will not change the Antzilla.antvar value.
az1 = Antzilla()
az1.antvar = "new var"
az1.PrintSuperAntvar()
>>> antzilla var
az1.PrintInstanceOfAntvar()
>>> new var
I can now create a new Antzilla instance with the original starting value because I never changed base class value
az2 = Antzilla()
az2.PrintSuperAntvar()
>>> antzilla var
az2.PrintInstanceOfAntvar()
>>> antzilla var
However if you were to change this super value then you would see that new Antzilla's start with this new value, but Antzilla's that have been already changed remain the same.
Antzilla.antvar = "newest var"
az3 = Antzilla()
az3.PrintSuperAntvar()
>>> newest var
az3.PrintInstanceOfAntvar()
>>> newest var
az1.PrintSuperAntvar()
>>> new var
WATCH OUT!!! notice what happens when we call az2!
az2.PrintSuperAntvar()
>>> newest var
az2 was never changed from the super variable, so when we changed Antzilla.antvar from "antzilla var" to "newest var", az2 will continue to cling to the super value.
How do we avoid this conflict!? Its simple!
Just add a constructor to your class that copies the super value, or a new value into its own variable..
If present, the __init__ function will be called if when you make a new Antzilla instance
class Antzilla:
antvar = "antzilla var"
def __init__(self):
self.antvar = Antzilla.antvar
...
You can also add the var as a requirement to your constructor such that every instance is unique.
class Antzilla:
antvar = "antzilla var"
def __init__(self, antvar ):
self.antvar = antvar
...
az1 = Antzilla("antzilla unique swag")
It is important to note however that when dealing with variables like lists, that you will need to specifically create a new list for every instance. Luckily the best place to do this is also the constructor.
So now getting back to your issue,.. for your two classes I would add constructors like this
For Colony:
class Colony(object):
workerAnts = 0
list = []
temp = []
foodAmount = 10
def __init__(self):
self.workerAnts = 0
self.list = []
self.temp = []
self.foodAmount = 10
....
For Ant
class Ant(object):
health = 10
def __init__(self):
self.health = 10
....
And finally what you need to do before getting into the math or logic errors is replace all the spots where you call a base or super variable with self or the name of the variable in the given scope.
ie, things like:
Colony.foodAmount -= 5
change to:
self.foodAmount -= 5
============================================================================
PS that spot where you write :
Colony.temp.append("ant")
is actually appending a string to your base list. You probably want to change that to the constructor for an Ant.. which returns a new instance of the Ant class and make it add the variable to an instance of colony instead of the base colony
self.temp.append(Ant())
============================================================================
Hope this helps!!
Cheers,
Cal