Simplifying State Machines (Python) - python

I want to know why State Machines use an intermediary "State" class at all (between the Concrete States and the Context). From the model below, this could be done with a simple variable.
For example, here's a simplified "containment" style state machine of sorts, which meets the same goal. What am I losing with this model, compared to using a traditional State Machine model?
Also, side question, these child classes are contained because they're indented and part of the parent (Lift) class. What is the difference to this style of inheritance vs un-indenting them and setting them to something like this: class FloorOne(Lift):
class Lift:
def __init__(self):
self.current_state = Lift.FloorOne(self)
def show_input_error(self):
print("Incorrect input")
class FloorOne:
def __init__(self, lift):
self.lift = lift
print("You are on Floor One")
def button_press(self, num):
self.transition(num)
def transition(self, num):
if num == 2:
self.lift.current_state = Lift.FloorTwo(self.lift)
elif num == 3:
self.lift.current_state = Lift.FloorThree(self.lift)
else:
self.lift.show_input_error()
class FloorTwo:
def __init__(self, lift):
self.lift = lift
print("You are on Floor Two")
def button_press(self, num):
self.transition(num)
def transition(self, num):
if num == 1:
self.lift.current_state = Lift.FloorOne(self.lift)
elif num == 3:
self.lift.current_state = Lift.FloorThree(self.lift)
else:
self.lift.show_input_error()
class FloorThree:
def __init__(self, lift):
self.lift = lift
print("You are on Floor Three")
def button_press(self, num):
self.transition(num)
def transition(self, num):
if num == 1:
self.lift.current_state = Lift.FloorOne(self.lift)
elif num == 2:
self.lift.current_state = Lift.FloorTwo(self.lift)
else:
self.lift.show_input_error()
lift = Lift()
while True:
goto = input("What floor would you like to go to?")
lift.current_state.button_press(int(goto))

If you define all the floor classes as subclasses from a common class State, you gain:
It is clear, from the code, which is the common interface of the concrete states. Even you can enforce an interface adding abstract methods which have to be overriden in the concrete classes in order to be able to be instantiated
You have to code less, because you can define the methods which are equal for all states once, in the State class. For example the button_press method.
Makes code easier to change.
Look at this code:
class Lift:
def __init__(self):
self.current_state = Lift.FloorOne(self)
def show_input_error(self):
print("Incorrect input")
class State:
def __init__(self, lift):
self.lift = lift
print(f'You are on Floor {self.floor_name}')
def button_press(self, num):
self.transition(num)
def transition(self, num):
if num != self.floor_num and num in [1,2,3]:
self.lift.current_state = [Lift.FloorOne,
Lift.FloorTwo,
Lift.FloorThree][num - 1](self.lift)
else:
self.lift.show_input_error()
class FloorOne(State):
floor_name = 'One'
floor_num = 1
class FloorTwo(State):
floor_name = 'Two'
floor_num = 2
class FloorThree(State):
floor_name = 'Three'
floor_num = 3
lift = Lift()
while True:
goto = input("What floor would you like to go to?")
lift.current_state.button_press(int(goto))
Now it is easier to add a floor.
If you want you can override any of the methods in a subclass for a different behavior:
class BrokenLift(State):
def transition(self, num):
print('broken lift!')

Related

Python class function, can't append to list

class A:
def __init__(self):
self._list = []
def addItem(self, x):
self._list.append(x)
def main():
while True:
option = int(input("Enter option: "))
if option == 1:
A().addItem("hello")
if option == 2:
print(A()._list)
main()
Sorry haven't been learning from python that long. Can someone explain to me why after option 1 is chosen, the list is still [] after i enter option 2?
Because you are calling different instances. Just introduce a variable here and you would get your desired result. The updated code is
class A:
def __init__(self):
self._list = []
def addItem(self, x):
self._list.append(x)
def main():
a = A()
while True:
option = int(input("Enter option: "))
if option == 1:
a.addItem("hello")
if option == 2:
print(a._list)
main()

How to replace an integer with a different class when it is assigned

I have a class called newInteger, and a variable called num, but I would like num to be a newInteger() instead of an int(). Code below.
class newInteger(int):
def __init__(self, value):
self.value = value
num = 10
I want the line num = 10 to act as if it is num = newInteger(10). Thanks to anyone who can help me with this.
You can run a small thread parallel to your main program that replaces all created integers to newInteger:
import threading
import time
class newInteger(int):
def __init__(self, value):
self.value = value
def __str__(self):
return "newInteger " + str(self.value)
def replace_int():
while True:
g = list(globals().items())
for n, v in g:
if type(v) == int:
globals()[n] = newInteger(v)
threading.Thread(target=replace_int, daemon=True).start()
num = 10
time.sleep(1)
print(num)
But this is unpythonic and will be realy hard to debug. You should just use a explicit conversion like #johnashu proposed
I am not sure if this is what you mean but if youassign the class to a variabl. then it will be an instance of that class..
example:
class newInteger(int):
def __init__(self, value):
self.value = value
num = 10
if num == 10:
num = newInteger(10)
prints:
hello

How can I inherit QTableWidget and my class MainWindow?

First off I'm relatively new to python and very new to PyQt. I'm taking my first steps into object oriented programming and I have been looking at online tutorials, but I'm stuck on a multiple inheritance issue.
I have class called CreateTable() whose purpose is to make a QTableWidget in my app window and when a row is clicked it opens a context menu and you can then choose the option to graph the selected row. Where the graph option is connected to another class Plotter().
My problem is that CreateTable needs to inherit both the QTableWidget from PyQt and my class MainWindow because I need to reference the layout variable in order to embed my graph into my application window.
My code for attempted inheritance is as follows and heavily borrows from here:How does Python's super() work with multiple inheritance?
class QTable(QTableWidget):
def __init__(self):
super(QTable, self).__init__()
class PassMain(MainWindow):
def __init__(self):
super(PassMain, self).__init__()
class PassMainTable(PassMain, QTable):
def __init__(self):
super(PassMainTable, self).__init__()
The main problem is when I try to place the graph inside my MainWindow layout.
self.vboxRight.addWidget(self.Graph)
Here is my code for creating the table and calling plotter
class CreateTable(PassMainTable): #QTableWidget
def __init__(self, Data, row, col, colHeaders, rowHeaders): #Index, ColumnHeaders
super(CreateTable, self).__init__()
self.setSelectionBehavior(self.SelectRows)
print("Start initialization")
self.ColHeader = colHeaders
self.setRowCount(row)
self.setColumnCount(col)
self.data = Data
self.setHorizontalHeaderLabels(colHeaders)
print("Right before for loop")
n = len(Data)
m = len(colHeaders)
for i in range(n):
DataValues = self.data.iloc[i,:]
print("values are {}".format(DataValues))
#m = len(values)
ConvertedVals = pd.to_numeric(DataValues)
ValList = DataValues.values.tolist()
print(ValList)
for j in range(0,m):
self.item = QTableWidgetItem(str(round(ValList[j],5)))
#print("{}, {}".format(i, j))
self.setItem(i,j, self.item)
def contextMenuEvent(self, event):
menu = QMenu(self)
graphAction = menu.addAction("Graph")
compareAction = menu.addAction("Compare")
scatterAction = menu.addAction("Plot types")
aboutAction = menu.addAction("about")
quitAction = menu.addAction("quit")
printAction = menu.addAction("Print Row")
action = menu.exec_(self.mapToGlobal(event.pos()))
if action == quitAction:
qApp.quit()
elif action == printAction:
self.selected = self.selectedItems()
n = len(self.selected)
print("n is {}".format(n))
for i in range(n):
self.selected[i] = str(self.selected[i].text())
for i in range(n):
self.selected[i] = float(self.selected[i])
print(self.selected)
elif action == graphAction:
self.selected = self.selectedItems()
n = len(self.selected)
for i in range(n):
self.selected[i] = str(self.selected[i].text())
for i in range(n):
self.selected[i] = float(self.selected[i])
print("right before plotter called")
self.Graph = Plotter(self.selected, self.ColHeader)
self.vboxRight.addWidget(self.Graph)
else:
print("u clicked something other than quit")
To make matters worse PyQt is catching all my errors as exceptions so all I get for errors is "Process finished with exit code 1"
if you need further reference to my full code I have provided a link here: https://github.com/Silvuurleaf/Data-Analysis-and-Visualization-GUI/blob/master/Plotter3.1.py
Thank you I appreciate any help y'all can give me.
In order to share data it is not necessary to inherit from the widget, just use signals, this is the natural way in PyQt to share data asynchronously. In your case for example we will create a signal called dataSignal, according to what is observed of your code you want to use the variables self.selected, self.ColHeader, the first one is of type list, and the second numpy.ndarray so with that We will build the signal:
class CreateTable(QTableWidget): #QTableWidget
dataSignal = pyqtSignal(list, np.ndarray)
def __init__(self, Data, row, col, colHeaders, rowHeaders): #Index, ColumnHeaders
super(CreateTable, self).__init__()
self.setSelectionBehavior(self.SelectRows)
print("Start initialization")
self.ColHeader = colHeaders
self.setRowCount(row)
self.setColumnCount(col)
self.data = Data
self.setHorizontalHeaderLabels(colHeaders)
print("Right before for loop")
n = len(Data)
m = len(colHeaders)
for i in range(n):
DataValues = self.data.iloc[i,:]
print("values are {}".format(DataValues))
#m = len(values)
ConvertedVals = pd.to_numeric(DataValues)
ValList = DataValues.values.tolist()
print(ValList)
for j in range(0,m):
self.item = QTableWidgetItem(str(round(ValList[j],5)))
#print("{}, {}".format(i, j))
self.setItem(i,j, self.item)
def contextMenuEvent(self, event):
menu = QMenu(self)
graphAction = menu.addAction("Graph")
compareAction = menu.addAction("Compare")
scatterAction = menu.addAction("Plot types")
aboutAction = menu.addAction("about")
quitAction = menu.addAction("quit")
printAction = menu.addAction("Print Row")
action = menu.exec_(self.mapToGlobal(event.pos()))
if action == quitAction:
qApp.quit()
elif action == printAction:
self.selected = self.selectedItems()
n = len(self.selected)
print("n is {}".format(n))
for i in range(n):
self.selected[i] = str(self.selected[i].text())
for i in range(n):
self.selected[i] = float(self.selected[i])
print(self.selected)
elif action == graphAction:
self.selected = self.selectedItems()
n = len(self.selected)
for i in range(n):
self.selected[i] = str(self.selected[i].text())
for i in range(n):
self.selected[i] = float(self.selected[i])
print("right before plotter called")
print(type(self.selected), type(self.ColHeader))
self.dataSignal.emit(self.selected, self.ColHeader)
else:
print("u clicked something other than quit")
Then in the MainWindow class we create a slot, in this create the Plotter object and add it to the layout.
def dataPlotter(self, x_data,y_data):
self.Graph = Plotter(x_data, y_data)
self.vboxRightBottom.addWidget(self.Graph)
To do this we connect the signal when we create the CreateTable object:
self.Table = CreateTable(self.BaseStats, row, col, colHeaders, rowHeaders)
self.Table.dataSignal.connect(self.dataPlotter)
self.vboxRightBottom.addWidget(self.Table)
The complete code is here.

Python - Classes - Self Not Defined

Below I'm attempting to make a simple Keygen as a first project. Somewhere I'm getting the error the Self has not been defined.
I'm guessing it's probably something easy
import random
class KeyGenerator():
def __init__(self):
length = 0
counter = 0
key = []
Letters = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z']
def KeyGen4(self):
while self.counter != self.length:
a = random.choice(self.Letters)
print a #test
r = (random.randint(0,1))
print r #test
if r == True:
a = a.upper()
else:
pass
self.key.append(a)
self.counter += 1
s = ''
self.key = s.join(key)
print self.key
return self.key
def start(self):
selection = raw_input('[K]eygen4, [C]ustom length Keygen or [N]umbers? >')
if selection == 'K' or 'k':
length = 4
keyGen4(self)
elif selection == 'N' or 'n':
KeyGenN(self)
elif selection == 'C' or 'c':
length = int(raw_input("Key Length: "))
#KeyGen4(self) # Change later after creating method with more options
start(self)
Your indention is wrong, but I assume this is only a copy-pasting issue.
That start(self) at the bottom doesn't make sense,
and indeed self is not defined there. You should create an instance of the class, and then call its start method:
KeyGenerator().start()
# or
key_gen = KeyGenerator()
key_gen.start()
You have two problems:
you miss indentation on every class-function
you must create an object of the class before you can call any of its functions
Your class should look like this
import random
class KeyGenerator():
def __init__(self):
length = 0
counter = 0
key = []
Letters = ['a','b','c','d','e']
def KeyGen4(self):
while self.counter != self.length:
a = random.choice(self.Letters)
print a #test
r = (random.randint(0,1))
print r #test
if r == True:
a = a.upper()
else:
pass
self.key.append(a)
self.counter += 1
s = ''
self.key = s.join(key)
print self.key
return self.key
def start(self):
selection = raw_input('[K]eygen4, [C]ustom length Keygen or [N]umbers? >')
if selection == 'K' or 'k':
length = 4
self.keyGen4()
elif selection == 'N' or 'n':
self.KeyGenN()
elif selection == 'C' or 'c':
length = int(raw_input("Key Length: "))
#KeyGen4(self) # Change later after creating method with more options
#now make an instance of your class
my_key_gen = KeyGenerator()
my_key_gen.start()
Please note that when calling class functions inside the class, you need to use self.FUNCNAME. All class functions should take "self" as argument. If that is their only argument then you simply call them with self.func(). If they take arguments you still ommit the self, as self.func(arg1, arg2)

Python function which needs to return a Class, returns None

So guys I have this class. It's a Treasure generator of static classes which return a Spell class, Weapon class, mana or health potion value.
The problem is, when I return the Value it returns None, but if I replace it with print, instead of return, then in prints it with no problems exactly as it in str function. How can I return the type, so it doesn't show me None ?
from random import randint
from weapon_class import Weapon
from spell_class import Spell
class Treasure:
#staticmethod
def generate_random_index(limit):
rand_index = randint(0, int(limit))
return rand_index
#staticmethod
def return_generated_treasure(max_mana, max_health):
# generate num from 0-3
rand_gen_num = Treasure.generate_random_index(3)
options = {
0: Treasure.generate_spell,
1: Treasure.generate_weapon,
2: Treasure.generate_mana_pot,
3: Treasure.generate_health_pot
}
# give arguments for mana and health potion functions only (for now)
if rand_gen_num == 2:
options[rand_gen_num](max_mana)
elif rand_gen_num == 3:
options[rand_gen_num](max_health)
else:
# call other functions, which doesn't need arguments,
# like generate spell and weapon
options[rand_gen_num]()
#staticmethod
def generate_spell():
with open('spell_names.txt', 'r') as f:
database_spell_names = f.read().replace('\n', '').split(',')
lst_len = len(database_spell_names) - 1
# generate number in range 0 - <spell names length>
rand_gen_num = Treasure.generate_random_index(lst_len)
spell_name = database_spell_names[rand_gen_num]
spell_mana_cost = randint(5, 35)
spell_damage = randint(5, 40)
cast_range = randint(1, 3)
# return spell
return Spell(spell_name, spell_damage, spell_mana_cost, cast_range)
#staticmethod
def generate_weapon():
with open('weapon_names.txt', 'r') as f:
database_weapon_names = f.read().replace('\n', '').split(',')
lst_len = len(database_weapon_names) - 1
rand_gen_num = Treasure.generate_random_index(lst_len)
weapon_name = database_weapon_names[rand_gen_num]
weapon_damage = randint(5, 40)
# return weapon
return Weapon(weapon_name, weapon_damage)
#staticmethod
def generate_mana_pot(max_mana):
max_possible_mana_limit = max_mana * 1/2
mana_portion = randint(0, int(max_possible_mana_limit))
return mana_portion
#staticmethod
def generate_health_pot(max_health):
max_possible_health_limit = max_health * 1/3
health_portion = randint(0, int(max_possible_health_limit))
return health_portion
def main():
award = Treasure.return_generated_treasure(100, 100)
print (award)
if __name__ == '__main__':
main()
return_generated_treasure doesn't return anything - there's no return keyword there.
You need to make outputs of other functions global. As they are returned within the function, their returns become local, and are then not propagated through the class or further returned.

Categories