How do I access class variables without changing them in python? - python

I'm new to programming so sorry for the basic question. I am trying to write a search algorithm for a class, and I thought creating a class for each search node would be helpful.
class Node(object):
def __init__(self, path_to_node, search_depth, current_state):
self.path_to_node = path_to_node
self.search_depth = search_depth
self.current_state = current_state
...
With some functions too. I am now trying to define a function outside of the class to create children nodes of a node and add them to a queue. node.current_state is a list
def bfs_expand(node, queuey, test_states):
# Node Queue List -> Queue List
# If legal move and not already in test states create and put children nodes
# into the queue and their state into test_states. Return queue and test states
# Copy original path, depth, and state to separate variables
original_path = node.path_to_node
original_depth = node.search_depth
original_state = node.current_state
# Check if up is legal, if so add new node to queue and state to test state
if node.is_legal_move('Up'):
up_state = original_state
a = up_state.index(0)
b = a - 3
up_state[a], up_state[b] = up_state[b], up_state[a]
if up_state not in test_states:
test_states.append(up_state)
up_node = Node(original_path + ['Up'], original_depth + 1, up_state)
queuey.put(up_node)
print(test_states)
print(original_state)
I then try to proceed through down, left and right with similar if statements, but they are messed up because the original_state has changed. When I print the original state after that up statement, it returns the up_state created in the if statement. I realize (well, I think) that this is because original_state, and therefore up_state, are actually calling node.current_state and do not store the list in a separate variable. How should I get the variable from a node to manipulate independently? Should I not even be using a class for something like this, maybe a dictionary? I don't need code written for me but a conceptual nudge would be greatly appreciated!

You should use copy.deepcopy if you want to avoid modifying the original
original_path = copy.deepcopy(node.path_to_node)
original_depth = copy.deepcopy(node.search_depth)
original_state = copy.deepcopy(node.current_state)
Or essentially whichever object you want to use as a "working copy" should be a deep copy of the original if you don't want to modify the original version of it.

Expanding a bit on #CoryKramer's answer: In Python, objects have reference semantics, which means that saying
a = b
where a and b are objects, makes both a and b references to the same object, meaning that changing a property on a will change that same property on b as well. In order to actually get a new object with the same properties as the old one, you should use copy.deepcopy as already stated. However, be careful when using that function. If your object contains a reference cycle (i.e.: It contains a reference to an object which contains a reference to itself), copy.deepcopy will lead to an infinite loop.
For this reason, there is also copy.copy, which does not follow object references contained in the object to copy.

Related

process value of an attribute of an object without changing it in python

Hello I am struggling with a Problem in my Python code.
My problem is the following: I want to copy the value of an attribute of an object.
I create the object agent, then in the function train_agent() I copy the value of the attribute state_vec to the variable last_state_vec. The Problem now is, when I change last_state_vec I automatically also change the attribute state_vec of agent state_vec = np.array(0,0,10,0,0). How can I only copy the value of state_vec, so that it doesn't get changed in this case. I want state_vec to stay the zero vector.
Here's some of my code:
class DQN():
def __init__(self)
self.state_vec = np.zeros(5)
agent = DQN()
def train_agent(agent):
last_state_vec = agent.state_vec
last_state_vec[2] = 10
return 0
Inside train_agent() function you can set last_state_vec to agent.state_vec.copy().
Currently you are initializing the variable last_state_vec with the reference of agent.state_vec.
So By replacing it with agent.state_vec.copy() should do the trick !
If you only need a shallow copy you can use np.copy, like so:
last_state_vec = agent.state_vec.copy()
That should work in this case since there are only integers in there.
In the event that you are storing objects in the state_vec you will need to use deepcopy, like so:
last_state_vec = copy.deepcopy(agent.state_vec)
You'll need to import copy for this.
The NumPy documentation gives examples of both types of copying.
https://numpy.org/doc/stable/reference/generated/numpy.copy.html

Selecting Maya objects through memory using classes

Selecting Maya objects through memory using classes
What is the problem with the below code:
import maya.cmds as cmds
class MakeObject:
def __init__(self):
self.grp = cmds.createNode('transform')
def make_cube(self):
self.cube = cmds.polyCube(n='cube')[0]
cmds.parent(self.cube, self.grp)
def selection(self):
cmds.select(self.cube)
x = MakeObject()
x.make_cube()
y = MakeObject()
y.make_cube()
x.selection()
Unlike pymel, cmds doesn't use a wrapper to work with nodes. Instead it uses strings which can be problematic since they don't update dynamically. For example, if you create a new node my_cube = cmds.polyCube(n='cube')[0] then rename it, the my_cube variable will still be pointing the object's older name.
This could also be a problem if there are multiple objects in the scene that share the same duplicate name. Maya usually auto-renames duplicate naming to avoid conflicts, but it's valid to have duplicate names in different hierarchies. In this case you would have to access it by using the node's long name. For example, an object named "cube" that's parented under another object named "root" would have a long name "|root|cube".
All this being said, yes, there is a problem with this code. It's making 2 cubes with the same name and re-parenting them to a different hierarchy. So there will be 2 nodes with the name "cube". When the select command is called, it's literately calling cmds.select("cube"), but since there's more than one "cube" Maya has no idea how to interpret that and then throws an error.
What can be done is to capture the object's new long name by doing this after it parents: self.cube = cmds.ls(sl=True, long=True)[0]
Now it's able to select the first cube as expected.

Python list scope

Let's examine this.
class SomeObject:
testList = []
def setList(self, data):
self.testList = data
def getList(self):
return self.testList
class UtilClass:
def populateList(self, foreign, str):
data = []
data.append(str)
foreign.setList(data)
def main():
data = SomeObject()
data2 = SomeObject()
util = UtilClass()
util.populateList(data, "Test 1")
util.populateList(data2, "Test 2")
util.populateList(data, "Test 3")
print(data.getList())
print(data2.getList())
if __name__=="__main__":
main()
What does the data object inside of main now contain, a copy of a the list constructed inside of populateList() or a reference to it?
Should I do something like list() or copy.copy() or copy.deepcopy()? The data object should go out of scope inside of populateList, right?
What happens when I pass another list to the util object? Does the list indata get altered or overwritten?
If the data inside of populateList go out of scope, why is it valid after the second call to populateList?
What does the data object inside of main now contain, a copy of a the list constructed inside of populateList() or a reference to it?
A reference to it. But since you do not pass the reference to another object. data is the only one that has a reference to that list.
Should I do something like list() or copy.copy() or copy.deepcopy()? The data object should go out of scope inside of populateList, right?
No, only if you want to construct a copy (for instance if you want to alter the list further later). The object will not be garbage collected because it is allocated on the heap. As long as there is one active variable that references it, it will stay alive.
What happens when I pass another list to the util object? Does the list indata get altered or overwritten?
No, util will no reference to the new list, the old list lives independently. If no other object referenced to the old list, the old list will eventually get garbage collected.
If the data inside of populateList go out of scope, why is it valid after the second call to populateList?
With out of scope programmers say that the list no longer listens to the data identifier. But the list still lives into memory. If other objects reference to it, they can alter/modify it, etc.

How do I change where a variable is pointing to within a function?

I have a python function that takes an object and either modifies it or replaces it.
def maybe_replace_corgi(corgi):
# If corgi is cute enough, modify it to keep it.
if corgi.cuteness > threshold:
corgi.will_keep = True
else:
# Corgi not cute enough! Instantiate new corgi.
corgi = Corgi(name="Woofus")
I know that objects are passed into python functions by reference. But what if I want to replace an object within a function entirely? As I want to do in my else statement? How do I make all references to the corgi object in my program point to this new corgi object?
The standard way would be to return a new corgi or the old corgi:
def maybe_replace_corgi(corgi):
if corgi.cuteness > threshold:
corgi.will_keep = True
return corgi
else:
return Corgi(name="Woofus")
my_corgi = Corgi(name="Rudolf")
my_corgi = maybe_replace_corgi(my_corgi)
The downside of this is, as mentioned by others: It doesn't replace all references, it only replaces that one reference of my_corgi. However, replacing all references is not possible.
Instead of replacing all references to your object, you could just edit your object to look just like a new one. Just replace all it's attributes' values. For this you could create a new method in Corgi class, which resets all attributes.
In most cases you don't need a new method, you already got one: If your __init__ method doesn't do anything too fancy (fe. increase Corgi.count variable or such) it can be called again to re-init all of your object's attributes.
def maybe_replace_corgi(corgi):
if corgi.cuteness > treshold:
corgi.will_keep = true
else:
corgi.__init__(name="Woofus")
You can't. The only things you can do are to mutate that object so it has the new value you want (as you do in the first part of your if statement), or return it, or store it somewhere that other code will look for it when it wants the value.
For instance, if you do your work in a class, and always set/access the corgi as self.corgi, then doing self.corgi = Corgi(name="Woofus") will work, since subsequent reads of self.corgi will read the new value. A similar effect can be achieved by having the caller do corgi = maybe_replace_corgi(corgi), and having maybe_replace_corgi return the old corgi or a new one.
In general you can't do things like "change all references everywhere in the program". If you are judicious in not creating tons of references to the same object, and instead create only a limited number of references in particular contexts, then you will have no problem changing those references when the time comes.

Deleting certain instances of a class attribute

I am working with classes in Python for the first time and I need to loop through my class attributes and delete certain instances under certain conditions. The problem is that I cannot find any examples of deleting instances of certain attributes. To be a little more specific, my class is phone_bills and one of my attributes is minutes_used and the instance would be minutes used of a specific month. Well, sometimes I need to delete that one month or one instance.
I am starting to wonder if working with classes is wrong for this particular project.
Here is some of my code (where i make the class and then at the bottom where i try to deltete an instance.
class MeterBill:
'components of MeterBill'
def __init__(self,IDC,Name,StartD,End_D,Mdays,Cons): #Name,StartD,End_D,Mdays,Cons):
self.IDC = IDC #idc
self.Name= Name #name
self.StartD = StartD #startd
self.End_D = End_D #end_d
self.Mdays = Mdays #modays
self.Cons = Cons #cons
def __repr__(self):
return repr((self.IDC,self.Name,self.StartD,self.End_D,self.Mdays,self.Cons))
#there is some other code here to read in the data then
e=len(bills); startlen=e;s=0
row=0; daysthresh=38; count=0
while count < startlen:
for row in range(s,e):
count = 1+ count
if bills[row-1].Mdays < daysthresh and bills[row-1].IDC==bills[row].IDC:
print bills[row-1],#row-1,meter[row-1]
bills[row].Mdays = bills[row-1].Mdays+bills[row].Mdays
bills[row].Cons = bills[row-1].Cons+bills[row].Cons
bills[row].StartD=bills[row-1].StartD
#del mybills.minutes_used
#bills=MeterBill()
del bills[row-1].Cons
the last 3 lines is me trying to delte an instance of my class at row-1 (using code from Peter Downs post). I want to delete this one line. I want to delete 1 single instance of each attribute that i defined.
so if I could get that del bill[row-1].cons to work then i would do it for all the other attributes at row-1.
Note you have to scroll to the right ot see my if statement.
I am starting to wonder if working with classes is wrong for this particular project.
No, certainly not, no worries :)
Lets say we have the following class:
class PhoneBills(object):
def __init__(self, minutes_used):
self.minutes_used = minutes_used
Then you can delete the attribute minutes_used simply by:
mybills = PhoneBills()
del mybills.minutes_used
Which would remove the attribute from your object mybills. All lookups would result in an exception. I'm not sure that this is what you want. Probably just setting minutes_used to 0 or None would be a better approach?
Using an object in this case isn't a bad idea, as others have pointed out. However, you have to think about the problem just a little bit differently in order to get the best use of these objects.
If you have a phone_bills object, then we assume its only responsibility is to manage a phone bill. It could be for a single month, it could be for an entire year - there's enough context in the object name that it could do both.
If it's managing a month-to-month bill, then what's required is, at the end of every month, the minutes used is recalculated, meaning that the value for the used minutes at this current point is reset, not deleted.
Now, unless your assignment specifically calls for you to delete the end-of-month total, then you're best served with resetting the value. The way to do this with Python objects is simple:
phone_bills.minutes_used = 0
Deleting means dereferencing the object, till its referencecounter reaches 0, so the garabage collector may free the allocated space for that particular object and effectivly destroying/deleting it.
What you want to do is set the appropriate attribute to None. By doing so, you reduce the refcounter by 1.
To illustrate what I mean, try the following:
import sys
ref = 'a Random object'
print sys.getrefcount(ref), id(ref)
newlist = [ref]
print sys.getrefcount(newlist[0]), id(newlist[0])
ref = None
print sys.getrefcount(newlist[0]), id(newlist[0])
newlist[0] = None
after the last line you have no reference to the underlying object, the refounter reaches 0 and the object gets collected by the garbage collector.
You may also use the del statement to express your intend clearly in your sourecode. e.g.: del june but then you also remove the identifier june from your namespace. Don't shoot the messanger! ;-)
I am starting to wonder if working with classes is wrong for this
particular project.
I believe that they may be unnecessary.
Instead of a class, try using dictionaries.
You could write a helper function to populate them, and it's very easy to remove a key from a dictionary.
The only reason to use a class is if you need instance methods - is that true?
Event then, you could rewrite them as regular functions.
def createPhoneBill(minutes_used):
return {
"minutes_used":minutes_used,
# you could put more attributes here, just add them as arguments to the function
}
As an added bonus, default values are much easier. Also, you get to use the dictionary.get(attr_name, default_value) function now, too.
Here's what deletion would look like:
Deleting an attribute:
mybills = createPhoneBill(5000):
if mybills["minutes_used"] > 2000:
del mybills["minutes_used"]
Deleting an 'instance':
mybills = createPhoneBill(5000):
if mybills["minutes_used"] > 3000:
del mybills
In Python, you don't delete objects--you simply remove any references towards them and allow the garbage collector to reclaim the memory they're holding.
Assigning phone_bills_obj.minutes_used = None would cause the garbage collector to remove the object referenced by phone_bills_ojb.minutes_used, in case the same object isn't being referenced anywhere else.

Categories