In a Python/Qt program, i want to notify the user when a monitored value is outside the expected range. To avoid false positives, the value should be outside the range several times in a row, and we should only notify the user once so it can be acted upon or dismissed.
Below would be my approach, but I am accumulating way too many class attributes that are only used in a single function. What is a cleaner way to implement this?
Is there a way for _counter and _enabled to attributes of the method check_value() only instead of the class TestScreen()?
class TestScreen(QDialog):
def __init__(self):
self._counter = 0
self._enabled = True
def check_value(self):
if self._enabled:
# don't act on the first out of bounds value
if value > target_value:
self._counter += 1
# notify the user when multiple out of bounds values measured
# only notify once
if self._counter > 5:
self._counter = 0
self._enabled = False
QMessageBox.Error("Value too high")
Related
I have a function that creates a player object but when referencing the object, I get a NameError. I think it is happening due to local scope but global should fix it...
I just started out OOP and this code is working in the python shell but it is not working in script mode.
endl = lambda a: print("\n"*a)
class Score:
_tie = 0
def __init__(self):
self._name = ""
self._wins = 0
self._loses = 0
def get_name(self):
print
self._name = input().upper()
def inc_score(self, wlt):
if wlt=="w": self._wins += 1
elif wlt=="l": self._loses += 1
elif wlt=="t": _tie += 1
else: raise ValueError("Bad Input")
def player_num(): #Gets number of players
while True:
clear()
endl(10)
print("1 player or 2 players?")
endl(5)
pnum = input('Enter 1 or 2: '.rjust(55))
try:
assert int(pnum) == 1 or int(pnum) == 2
clear()
return int(pnum)
except:
print("\n\nPlease enter 1 or 2.")
def create_player(): #Creates players
global p1
p1 = Score()
yield 0 #stops here if there is only 1 player
global p2
p2 = Score()
def pr_(): #testing object
input(p1._wins)
input(p2._wins)
for i in range(player_num()):
create_player()
input(p1)
input(p1._wins())
pr_()
wherever I reference p1 I should get the required object attributes but I'm getting this error
Traceback (most recent call last):
File "G:/Python/TicTacTwo.py", line 83, in <module>
input(p1)
NameError: name 'p1' is not defined
Your issue is not with global but with the yield in create_player(), which turns the function into a generator.
What you could do:
Actually run through the generator, by executing list(create_player()) (not nice, but works).
But I suggest you re-design your code instead, e.g. by calling the method with the number of players:
def create_player(num): #Creates players
if num >= 1:
global p1
p1 = Score()
if num >= 2:
global p2
p2 = Score()
If you fix this issue, the next issues will be
1) input(p1) will print the string representation of p1 and the input will be lost, you probably want p1.get_name() instead.
2) input(p1._wins()) will raise TypeError: 'int' object is not callable
I will redesign the app to introduce really powerful python constructs that may help you when getting into OOP.
Your objects are going to represent players, then don't call them Score, call them Player.
Using _tie like that makes it a class variable, so the value is shared for all the players. With only two participants this may be true but this will come to hurt you when you try to extend to more players. Keep it as a instance variable.
I am a fan of __slots__. It is a class special variable that tells the instance variables what attributes they can have. This will prevent to insert new attributes by mistake and also improve the memory needed for each instance, you can remove this line and it will work but I suggest you leave it. __slots__ is any kind of iterable. Using tuples as they are inmutable is my recomendation.
Properties are also a really nice feature. They will act as instance attribute but allow you to specify how they behave when you get the value (a = instance.property), assign them a value (instance.property = value), or delete the value (del instance.property). Name seems to be a really nice fit for a property. The getter will just return the value stored in _name, the setter will remove the leading and trailing spaces and will capitalize the first letter of each word, and the deletter will set the default name again.
Using a single function to compute a result is not very descriptive. Let's do it with 3 functions.
The code could look like this:
# DEFAULT_NAME is a contant so that we only have to modify it here if we want another
# default name instead of having to change it in several places
DEFAULT_NAME = "Unknown"
class Player:
# ( and ) are not needed but I'll keep them for clarity
__slots__ = ("_name", "_wins", "_loses", "_ties")
# We give a default name in case none is provided when the instance is built
def __init__(self, name=DEFAULT_NAME):
self._name = name
self._wins = 0
self._loses = 0
self._ties = 0
# This is part of the name property, more specifically the getter and the documentation
#property
def name(self):
""" The name of the player """
return self._name
# This is the setter of the name property, it removes spaces with .strip() and
# capitalizes first letters of each word with .title()
#name.setter
def name(self, name):
self._name = name.strip().title()
# This is the last part, the deleter, that assigns the default name again
#name.deleter
def name(self):
self._name = DEFAULT_NAME
def won(self):
self._wins += 1
def lost(self):
self._loses += 1
def tied(self):
self._ties += 1
Now that's all we need for the player itself. The game should have a different class where the players are created.
class Game:
_min_players = 1
_max_players = 2
def __init__(self, players):
# Check that the number of players is correct
if not(self._min_players <= players <= self._max_players):
raise ValueError("Number of players is invalid")
self._players = []
for i in range(1, players+1):
self._players.append(Player(input("Insert player {}'s name: ".format(i))))
#property
def players(self):
# We return a copy of the list to avoid mutating the inner list
return self._players.copy()
Now the game would be created as follows:
def new_game():
return Game(int(input("How many players? ")))
After that you would create new methods for the game like playing matches that will call the players won, lost or tied method, etc.
I hope that some of the concepts introduced here are useful for you, like properties, slots, delegating object creation to the owner object, etc.
I am using Transitions, a very useful FSM tool for python. I would like to make the states more, um, stateful... so that variables can be local to states, and their values changed when the state is entered or left. I am ending up with a fair number of instance variables in the machine - I really want some of those values in the state (how long have I been in this state, for example). They aren't attributes of the model, they are attributes of progress through the states.
I wonder if there is a 'best way' to do this? Subclass State?
thanks
I am not aware of a 'best way' but a reasonable approach depends on what you want to achieve. You can either a) subclass State, b) decorate initialised states or c) manually initialise (subclassed) states and pass these to the machine.
A) If every state has the same attributes you can subclass states as you suggested:
import transitions.extensions.nesting as nesting
class CounterState(nesting.NestedState):
def __init__(self, *args, **kwargs):
super(CounterState, self).__init__(*args, **kwargs)
self.entered = self.exited = 0
def enter(self, event_data):
self.entered += 1
def exit(self, event_data):
self.exited += 1
def __str__(self):
return "State {0} has been entered {1} times and exited {2} times".format(self.name, self.entered, self.exited)
class CounterMachine(nesting.HierarchicalMachine):
#staticmethod
def _create_state(*args, **kwargs):
return CounterState(*args, **kwargs)
machine = CounterMachine(states=['A', 'B'], initial='A')
a = machine.get_state('A')
b = machine.get_state('B')
print(a) # >>> State A has been entered 0 times and exited 0 times
machine.to_B()
print(a) # >>> State A has been entered 0 times and exited 1 times
print(b) # >>> State B has been entered 1 times and exited 0 times
I have used NestedMachine here because _create_state is not available in Machine so far. Update: Starting from version 0.4.4 it is also available for Machine.
B) Another approach involves some decoration of the initiated state objects by the model:
from transitions import Machine
class Model(object):
def __init__(self):
self.machine = Machine(model=self, states=['A', 'B'], initial='A',
before_state_change='exit_state',
after_state_change='enter_state')
# loop through all the states and attach attributes
for state in self.machine.states.values():
state.entered = 0
state.exited = 0
def enter_state(self):
# retrieve the state object by name
self.machine.get_state(self.state).entered += 1
def exit_state(self):
self.machine.get_state(self.state).exited += 1
def print_state(state):
print("State {0} has been entered {1} times and exited {2} times".format(state.name, state.entered, state.exited))
m = Model()
a = m.machine.get_state('A')
b = m.machine.get_state('B')
print_state(a)
m.to_B()
print_state(a)
print_state(b)
C) In cases where every state has to be treated individually, you can initiate the states manually and pass the instances to the machine instead of the names:
from transitions import Machine, State
class TicketState(State):
def __init__(self, name, tickets):
super(TicketState, self).__init__(name)
self.tickets = tickets
class Model(object):
def __init__(self):
# Using our own state
a = TicketState('A', 10)
# Setting tickets ourselves
b = State('B')
b.tickets = 3
self.machine = Machine(self, states=[a, b], initial='A',
before_state_change='decrease_tickets')
def tickets_left(self):
return self.machine.get_state(self.state).tickets > 0
def decrease_tickets(self):
s = self.machine.get_state(self.state)
s.tickets -= 1
if s.tickets < 0:
raise Exception('No Tickets left!')
print("State {0} has {1} tickets left.".format(s.name, s.tickets))
m = Model()
m.to_B() # >>> State A has 9 tickets left.
m.to_A() # >>> State B has 2 tickets left.
The amount of attributes and names could differ here of course. Instead of using the machine callback before_state_change, you can also pass on_enter/exit callbacks to the State object to treat each state individually during transitions. Or subclass State.enter(self, event_data) if you just require a set of different state types like TimedState and/or CounterState.
class A:
def getTotal(self)
self.__total = 0
for each in self.__hand:
if each.getValue() == 1 and self.__total > 10:
self.__total += 1
elif each.getValue() == 1 and self.__total <10:
self.__total += 11
elif each.getValue() != 1:
self.__total += each.getValue()
return self.__total
class B:
def getTotal(self):
return A.getTotal()
This isn't working for me. How can I get class A to return a total when called from class B's method
You cannot just call A.getTotal() because it is an unbound method; there is nothing to bind self to as you didn't give it an instance of A, where you'd have state. You need to call it on an instance of A. We just need to find one first.
From the comments I understand B to the the player, and A is a hand of cards for that player. Presumably in a game a player can play end up with more hands as multiple rounds are played.
In that case you'd have a reference from the player to the hand, like self.hand. That'd be an instance of A and you can call getTotal() on that instead:
class B:
def getTotal(self):
return self.hand.getTotal()
It may be confusing here that A also has a __hand attribute; that's perhaps not the best name for that attribute, as it is the list of cards for the current hand. It could perhaps better be named __cards in that case.
B.getTotal() is not strictly needed; you could also just use instance_of_B.hand.getTotal(), e.g. reach right into the instance attributes and call getTotal() directly on the hand. But that'd perhaps reveal too much about how the class handles hands, and perhaps you want to handle different cases, like return 0 if there is the possibility that at some points in the program is no hand at all.
And another thing: __total in A.getTotal() is a local variable; you don't need to use an attribute on self there. Remove the self. prefix, and just name it total:
def getTotal(self)
total = 0
for each in self.__hand:
if each.getValue() == 1:
if total > 10:
total += 1
else:
total += 11
else:
total += each.getValue()
return total
I simplified the function logic a little too.
I'm trying to implement trie in python. I'm using dictionaries+classes instead of lists (I know it's not optimal, but I'm just trying to make it work at all).
After debugging I found out that each layer has all letters in dictionary. I cannot understand why.
Here is my code (implementation is 100% most basic, straightforward):
class lttr:
finish = 0
pointers = {} #for letters to reference class instance
eps = lttr()
def add(word):
global eps
last = eps
for ind,x in enumerate(word):
if last.pointers.get(x,None):
last = last.pointers[x]
else:
last.pointers[x] = lttr()
last=last.pointers[x]
last.finish=1
def lookup(word):
global eps
last=eps
for ind,x in enumerate(word):
if last.pointers.get(x,None):
last=last.pointers[x]
else:
return False
return bool(last.finish)
add("pear")
print lookup("ar") #prints True ... but why?
I'm guessing you intended for each individual lttr instance to have its own unique values for finish and pointers. In which case, you need to declare them as attributes of self inside __init__, rather than just defining them at the class scope.
class lttr:
def __init__(self):
self.finish = 0
self.pointers = {} #for letters to reference class instance
Now your script will print False as expected.
Your lttr class has class variables, but you want instance variables. Class variables are unique across all instances of that class, so you only have one pointers object.
class lttr:
finish = 0
pointers = {} #for letters to reference class instance
What you want is
class lttr:
def __init__(self):
self.finish = 0
self.pointers = {}
and that works as expected.
I have been subclassing an Python's random number generator to make a generator that doesn't repeat results (it's going to be used to generate unique id's for a simulator) and I was just testing to see if it was consistent in it's behavior after it has been loaded from a previours state
Before people ask:
It's a singleton class
No there's nothing else that should be using that instance (a tear down sees to that)
Yes I tested it without the singleton instance to check
and yes when I create this subclass I do call a new instance ( super(nrRand,self).__init__())
And yes according to another post I should get consistent results see: Rolling back the random number generator in python?
Below is my test code:
def test_stateSavingConsitantcy(self):
start = int(self.r.random())
for i in xrange(start):
self.r.random()
state = self.r.getstate()
next = self.r.random()
self.r.setstate(state)
nnext = self.r.random()
self.assertEqual(next, nnext, "Number generation not constant got {0} expecting {1}".format(nnext,next))
Any help that can be provided would greatly appreciated
EDIT:
Here is my subclass as requested
class Singleton(type):
_instances = {}
def __call__(self, *args, **kwargs):
if self not in self._instances:
self._instances[self] = super(Singleton,self).__call__(*args,**kwargs)
return self._instances[self]
class nrRand(Random):
__metaclass__ = Singleton
'''
classdocs
'''
def __init__(self):
'''
Constructor
'''
super(nrRand,self).__init__()
self.previous = []
def random(self):
n = super(nrRand,self).random()
while n in self.previous:
n = super(nrRand,self).random()
self.previous.append(n)
return n
def seed(self,x):
if x is None:
x = long(time.time()*1000)
self.previous = []
count = x
nSeed = 0
while count < 0:
nSeed = super(nrRand,self).random()
count -= 1
super(nrRand,self).seed(nSeed)
while nSeed < 0:
super(nrRand,self).seed(nSeed)
count -= 1
def getstate(self):
return (self.previous, super(nrRand,self).getstate())
def setstate(self,state):
self.previous = state[0]
super(nrRand,self).setstate(state[1])
getstate and setstate only manipulate the state the Random class knows about; neither method knows that you also need to roll back the set of previously-generated numbers. You're rolling back the state inherited from Random, but then the object sees that it's already produced the next number and skips it. If you want getstate and setstate to work properly, you'll have to override them to set the state of the set of already-generated numbers.
UPDATE:
def getstate(self):
return (self.previous, super(nrRand,self).getstate())
This shouldn't directly use self.previous. Since you don't make a copy, you're returning the actual object used to keep track of what numbers have been produced. When the RNG produces a new number, the state returned by getstate reflects the new number. You need to copy self.previous, like so:
def getstate(self):
return (self.previous[:], super(nrRand, self).getstate())
I also recommend making a copy in setstate:
def setstate(self, state):
previous, parent_state = state
self.previous = previous[:]
super(nrRand, self).setstate(parent_state)