Python object state tracking idiom - python

I have an object that acts as an interface to a remote system, and I need to keep track of various states. Which is the better or more pythonic way of doing this? would one way be preferable if the list of states gets really long or complicated?
A. with object attributes:
class Session (object):
def__init__(self):
self.a_set = False
self.b_set = False
def set_a(self):
self.a_set = True
B. with a dict that represents the state:
class Session (object):
def__init__(self):
self.state = {}
def set_a(self):
self.state['a_set'] = True
Additional Details:
I originally set up a state dict, so that I could do a quick reset:
def reset_state(self):
for k in self.state:
self.state[k] = False
I may end up with a bunch of sub-classes that will need to track additional states, but still reset them all at once.
I just want to make sure I'm not shooting myself in the foot, or doing anything really weird/anti-pattern.

From the user perspective, it should look like the first option; we should be able to use:
if session.a_set:
do_something()
There are a couple different ways to implement that:
# simple method
class Session(object):
def __init__(self):
self.a_set = False
self.b_set = False
def reset(self):
self.b_set = False
However, if you have more than a couple hand-fulls of variables this would quickly become a maintenance nightmare.
I suggest the class decorator approach, with a couple helpers:
# not strictly necessary
class Persist(object):
"""
Do not clear this variable with reset().
"""
def __init__(self, val):
self.val = val
# necessary
class Reset(object):
"""
Clear this variable with reset().
"""
def __init__(self, val):
self.val = val
def BuildResets(cls):
reset = cls.resetable = []
persist = cls.persistent = []
for name, obj in cls.__dict__.items():
if isinstance(obj, Reset):
setattr(cls, name, obj.value)
reset.append(name)
elif isinstance(obj, Persist):
setattr(cls, name, obj.value)
persist.append(name)
return cls
and in use:
#BuildResets
class Session(object):
self.a_set = Persist(False)
self.b_set = Reset(False)
def reset(self):
for name in self.resetable:
setattr(self, name, False)

Related

How to have the same updated value of a Parent class be passed down to a inner class?

I need to access the value of an attribute defined at the parent class inside an inner class, here's the code:
class main(object):
def __init__(self):
self.session_id = None
self.devices = self.Devices(self.session_id)
class Devices(object):
def __init__(self, session_id):
self.session_id = session_id
And here's how I would like to use it:
>>> m = main()
>>> m.session_id = 1
>>> m.session_id
1
>>> m.devices.session_id
>>>
My expectation is that m.devices.session_id will always have the exact same value as m.session_id. I understand that at this point when I instantiate the inner class the session_id value is passed down as None because that's how it was initiated but I'm not sure how I can keep both values the same without doing something very ugly like:
m.devices.session_id = m.session_id
outside the class code.
How can I accomplish that inside the class itself ?
The other answer works, but I think this is a better design: lose the nested class, and add a getter on the device object to lookup a backref:
class Main(object):
def __init__(self):
self.session_id = None
self.devices = Devices(main_obj=self)
class Devices(object):
def __init__(self, main_obj):
self.main_obj = main_obj
...
#property
def session_id(self):
return self.main_obj.session_id
The difference here is that you're not storing the same data twice, so they can not get out of sync - there is only one "source of truth" for the session_id (on main object).
In the earlier answer, the data is actually stored in two different namespaces and will get out of sync as easily as m.devices.session_id = 123.
You can do it like this:
class main(object):
def __init__(self):
self._session_id = None
self.devices = self.Devices(self._session_id)
#property
def session_id(self):
return self._session_id
#session_id.setter
def session_id(self, value):
self._session_id = self.devices.session_id = value
class Devices(object):
def __init__(self, session_id):
self.session_id = session_id

Can class instances be accessed via an index in python?

Consider for example that we have a class 'Agent' as below:
class Agent:
def __init__(self, number):
self.position = []
self.number = number
for i in range(number):
self.position.append([0, 0])
I can make an instance of the class by:
agent = Agent(10)
and then access the i'th agent's position by:
agent.position[i]
However, this does not seem elegant enough and to me it's a bit counter-intuitive. Instead I want to index the class instance itself. For example:
pos_i = agent[i].position
which should return the same answer as the one-line code above. Is there a way to accomplish this?
If you want to do that, you just need a class-level container, with all instances.
Since your positions, given your example, are created in an arbitrary order, I'd suggest using a dictionary.
You can just fill the class-level "position" dictionary. You could then just implement the __getitem__ method to retrieve elements from this dictionary:
class Agent:
position = {}
def __new__(cls, pos):
if pos in cls.position:
return cls.position[pos]
instance = super().__new__(cls)
cls.position[pos] = instance
return instance
def __getitem__(self, item):
return self.position[pos]
This, however, will only allow you to retrieve an instance given the position from an instance - i.e.:
agent_5 = Agent(5)
agent_10 = agent_5[10]
would work, but not:
agent_10 = Agent[10]
If you want that, you have to use a custom metaclass, and put the __getitem__ method there:
class MAgent(type):
def __getitem__(cls, item):
return cls.position[pos]
class Agent(metaclass=MAgent):
position = {}
def __new__(cls, pos):
if pos in cls.position:
return cls.position[pos]
instance = super().__new__(cls)
cls.position[pos] = instance
return instance
If you want to overload the indexing operator just overload the __getitem__ method in the class.
class Agent:
def __getitem__(self, key):
return self.position[key]
>>> myobj = MyClass()
>>> myobj[3]

How to restrict object creation

Consider following example
class Key:
def __init__(self, s):
self.s = s
d = {}
for x in range(1, 10000):
t = Key(x)
d[t] = x
This will create 10000 keys. Is it possible to control the object creation of class key, for example we cannot create more than 5 objects of key class. The loop should not be changed in any ways.
You can control how, or how many objects are created by giving your class a __new__ method:
class Key(object):
_count = 0
def __new__(cls, s):
if cls._count == 5:
raise TypeError('Too many keys created')
cls._count += 1
return super(Key, cls).__new__(cls, s)
def __init__(self,s):
self.s = s
Key.__new__() is called to create a new instance; here I keep a count of how many are created, and if there are too many, an exception is raised. You could also keep a pool of instances in a dictionary, or control creating of new instance in other ways.
Note that this only works for new-style classes, inheriting from object.
You can also use a metaclass approach
import weakref
import random
class FiveElementType(type):
def __init__(self, name, bases, d):
super(FiveElementType, self).__init__(name, bases, d)
self.__cache = weakref.WeakValueDictionary()
def __call__(self, *args):
if len(self.__cache) == 5:
return random.choice(self.__cache.values())
else:
obj = super(FiveElementType, self).__call__(*args)
self.__cache[len(self.__cache)] = obj
return obj
class Key(object):
__metaclass__ = FiveElementType
def __init__(self, s):
self.s = s
You can choose a random element or select it on the base of stored index. In this approach your loop don't fail with an exception that can be right or not, depending on your intention.

Add a decorator to existing builtin class method in python

I've got a class which contains a number of lists where whenever something is added to one of the lists, I need to trigger a change to the instance's state. I've created a simple demonstration class below to try to demonstrate what I'm trying to do.
Suppose I have a class like this:
class MyClass:
added = False
def _decorator(self, f):
def func(item):
added = true
return f(item)
return func
def __init__(self):
self.list = [1, 2, 3]
self.list.append = self._decorator(self.list.append)
Since a list is built in, I cannot change it's .append method
cls = MyClass() #gives me an AttributeError since '.append' is readonly
Ideally, I could do the following:
cls = MyClass()
cls.list.append(4)
cls.added #would be true
How should I go about this? Would subclassing list allow me to change it's behavior in this way? If so, how would I pass in the class's state without changing the methods signature?
Thanks!
You cannot monkey-patch builtins, so subclassing is the only way (and actually better and cleaner IMHO). I'd go for something like this:
class CustomList(list):
def __init__(self, parent_instance, *args, **kwargs):
super(CustomList, self).__init__(*args, **kwargs)
self.parent_instance = parent_instance
def append(self, item):
self.parent_instance.added = True
super(CustomList, self).append(item)
class MyClass(object):
added = False
def __init__(self):
self.list = CustomList(self, [1,2,3])
c = MyClass()
print c.added # False
c.list.append(4)
print c.added # True
Would this suit your needs?
class MyClass(object):
added = False
def __init__(self):
self.list = [1,2,3]
def append(self, obj):
self.added = True
self.list.append(obj)
cls = MyClass()
cls.append(4)
cls.added #true
It might be helpful to know what exactly you're trying to achieve.

Python class and variables

Learning Python and I ran into some problems when I was working on making a linked list class.
This is just a quick node and dirty node class. In java I would of down private Node next and private int val but I only knew of global as the python cousin. How does this look?
#class Node class
class Node(object):
global next
global val
def __init__(self):
next
val
def setNext(self, aNext):
self.next = aNext
def getNext(self):
return self.next
def setVal(self, aVal):
self.val = aVal
def getVal(self):
return self.val
Then I tried to use a Node in another class with
from Node import *
head = Node()
How ever I am getting an error of undefined variable. Sorry for the simple question just new to python. Appreciate the help.
I would implement this this way:
class Node(object):
def __init__(self, next=None, val=None):
self.next = next
self.val = val
That's it. No getters or setters - Python doesn't use them. Instead, you refactor into a property if you need to move away from the basic attribute reference logic.
You can then create nodes with or without values or successors:
tailnode = Node()
tailnode.val = 'foo'
midnode = Node(val='bar')
midnode.next = tailnode
headnode = Node(val='baz', next=midnode)
You don't need the "global val" / "global next" .. It's a mistake even.
instead just write
val = None
next = None
and initiate them in the __init__()
Meaning, the first lines in your class should be like:
class Node(object):
# You can choose whether to initialize the variables in the c'tor or using your setter methods
def __init__(self, val=None, next=None):
self.next = next
self.val = val
If you really want private variables in Python… then you don't want private variables, and should read Peter DeGlopper's answer.
If you still really, really want private variables in Python… well, you can't have them. But you can have "cooperatively private" variables—variables that nobody will find unless they go looking for them, and that won't clutter the screen when you introspect things in the interpreter, and so on, and, most importantly, that Python programmers know, by convention, that they aren't supposed to touch. All you have to do is start the name with an underscore.
However, your code isn't creating member variables at all, for a number of reasons.
First, global does not declare or define a variable; all it does is tell Python, "when you see this variable later, don't use the normal rules to figure out if it's local or global, always use the global copy". You still have to assign a value to the variable somewhere; otherwise, you'll get a NameError.
Next, variables that you assign in the class definition are class members—similar to Java's static members, although not identical. Each class member is shared by all instances of the class. That's not what you want here; each Node is supposed to have its own separate val and next, not share one with all other Nodes, right?
Normal instance member variables are always accessed through dot syntax—as self.foo from inside the class's methods, or as spam.foo from outside.
So, where do you declare those? You don't. Python doesn't declare anything. You can add new members to an object at any time. The usual way to create a standard set of instance members is in the __init__ method:
class Node(object):
def __init__(self):
self._next = None
self._val = None
def setNext(self, aNext):
self._next = aNext
def getNext(self):
return self._next
def setVal(self, aVal):
self._val = aVal
def getVal(self):
return self._val
But really, you can just let the setters create them. That way, you'll catch the error if someone calls getNext without having called setNext first (which is, I assume, illegal).
class Node(object):
def setNext(self, aNext):
self._next = aNext
def getNext(self):
return self._next
def setVal(self, aVal):
self._val = aVal
def getVal(self):
return self._val
Or, alternatively, force the user to initialize the object with valid values at construction time:
def __init__(self, next, val):
self._next = next
self._val = val
Again, there's no good reason to use setters and getters in the first place in Python.
So, the simplest implementation of your class is:
class Node(object):
pass
While the most Pythonic is:
class Node(object):
def __init__(self, next, val):
self.next = next
self.val = val
… which, you'll notice, is Peter DeGlopper's answer, which, as I said at the start, is probably what you want. :)
Python doesn't really use private variables.
Something like this would be best:
class Node(object):
def __init__(self):
self.val = None
self.next = None
Then, you make and set the node like this:
>>> node = Node()
>>> node.val = 5
>>> node2 = Node()
>>> node2 = 1
>>> node.next = node2
>>> node.next.val
1
If you want to create node with Node(5, Node(1)), use:
class Node(object):
def __init__(self, value=None, next=None):
self.value = value
self.next = next

Categories