Python: Object does not support indexing - python

Yes, this question has been asked before. No, none of the answers I read could fix the problem I have.
I'm trying to create a little Bounce game. I've created the bricks like this:
def __init__(self,canvas):
self.canvas = canvas
self.brick1 = canvas.create_rectangle(0,0,50,20,fill=random_fill_colour(),outline=random_fill_colour())
self.brick2 = canvas.create_rectangle(50,0,100,20,fill=random_fill_colour(),outline=random_fill_colour())
self.brick3 = canvas.create_rectangle(100,0,150,20,fill=random_fill_colour(),outline=random_fill_colour())
self.brick4 = canvas.create_rectangle(150,0,200,20,fill=random_fill_colour(),outline=random_fill_colour())
self.brick5 = canvas.create_rectangle(200,0,250,20,fill=random_fill_colour(),outline=random_fill_colour())
self.brick6 = canvas.create_rectangle(250,0,300,20,fill=random_fill_colour(),outline=random_fill_colour())
self.brick7 = canvas.create_rectangle(300,0,350,20,fill=random_fill_colour(),outline=random_fill_colour())
self.brick8 = canvas.create_rectangle(350,0,400,20,fill=random_fill_colour(),outline=random_fill_colour())
self.brick9 = canvas.create_rectangle(400,0,450,20,fill=random_fill_colour(),outline=random_fill_colour())
self.brick10 = canvas.create_rectangle(450,0,500,20,fill=random_fill_colour(),outline=random_fill_colour())
self.bricksId = [self.brick1,self.brick2,self.brick3,self.brick4,self.brick5,self.brick6,self.brick7,self.brick8,self.brick9,self.brick10]
And I'm trying to reference the ID of bricksId[0] over here:
self.hit_brick(pos,self.bricks.bricksId[0])
Earlier, in the __init__, I define bricks as bricks, which is defined as Brick(canvas). However, the error states:
TypeError: 'Brick' object does not support indexing
In the answers to the other questions of this subject, I cannot find any that help me access bricks.bricksId[0].

In order for the Brick object to be indexable, you must implement the methods:
__getitem__
__setitem__
__delitem__
You don't need all of them, only the ones you use.
However, this seems like a case of self.bricks being a brick instead of a list of bricks. A list of bricks is indexable; however, a brick itself is not unless you implement the methods above.
Check this for reference.
In order to be able to call self.bricks.bricksId[number] when I needed:
def __getitem__(self,index):
return self.bricks.bricksId[index]
def __setitem__(self,index,value):
self.bricks.bricksId[index] = value

Related

How to set an attirbutes list when creating new object

Hope the title was not too confusing. I've been working on program and I have created so attribut with a default list in it. The thing is I don't know how to do modification to that list when I create this object. Thank you
def __init__(self,nm,pm,adss):
self._nom = nm
self._prenom = pm
self._adresse = adss
self._bonus = [0,0,0,0]```
employer1 = Employe("nom","prenom","adresse",[0,250,50,10] #For exemple I wanted to set
#the list for that one. But im not sure if I can do it like that or if I might need to create a new function for it.
The current version would not work, since you are not passing that list as a parameter to the initialisation function. Your __init__ function would need to look like this:
def __init__(self,nm,pm,adss,bns):
self._nom = nm
self._prenom = pm
self._adresse = adss
self._bonus = bns
Then the initialiser knows that it should expect a fourth parameter to apply to the _bonus attribute.
Note that this will only set the attribute when creating the object. To change it once the object is created, you should create a setter function.
I'm not certain from your question if you want a default set for the bonus as standard. If so, see the comments on your question for details on how to do that.
You could give an immutable default value to the bonus argument, then assign to self._bonus depending on whether or not a value was passed.
It is usually good practice to make a copy of the mutable parameters passed as argument to an object.
def __init__(self, nom, prenom, adresse, bonus=None):
self._nom = nom
self._prenom = prenom
self._adresse = adresse
self._bonus = [] if bonus is None else bonus[:] # make a copy of the mutable parameter
def __init__(self, nom, prenom, adresse, bonus=None):
self._nom = nom
self._prenom = prenom
self._adresse = adresse
self._bonus = bonus if bonus else [0, 0, 0, 0]
Reasons that this answers the question:
You don't want to make a list literal (one of these: []) be the default parameter to a function/method call. This is the immutable default value bug everyone's talking about.
Setting the bonus parameter's default to None and then performing a check to see if a different value was passed is the way to go.
The cleanest way to make a check on the value of the bonus parameter is shown above.

Assigning an (OOP) object to another

I was trying to assign a Python object to another in-place using a member function such as replace_object() below. However, as you can see, object_A remains unchanged and the only way to copy object_B is to create an entirely new object object_C, which defeats the purpose of in-place assignment.
What is going on here and how can I make the assignment in-place?
class some_class():
def __init__(self, attribute):
self.attribute = attribute
def replace_object(self, new_object):
self = new_object
# Does this line even have any effect?
self.attribute = new_object.attribute
self.new_attribute = 'triangle'
return self
object_A = some_class('yellow')
print(object_A.attribute) # yellow
object_B = some_class('green')
object_C = object_A.replace_object(object_B)
print(object_A.attribute) # yellow
print(object_C.attribute) # green
#print(object_A.new_attribute) # AttributeError!
print(object_B.new_attribute) # triangle
print(object_C.new_attribute) # triangle
I also tried to play around with deep copies using copy.copy(), but to no avail.
An interesting twist to this is that if I replace
object_C = object_A.replace_object(object_B)
with
object_A = object_A.replace_object(object_B)
then I get what I want. But why can't the same result be achieved by the statement self = new_object statement within replace_object()?
PS: I have a very good reason to do this in-place assignment, so although it may not be best practice in general, just go along with me here.
You can't 'assign an object to another'. You can assign new and existing objects to new and existing names.
self = new_object only says 'from now on the name self will refer to new_object', and does nothing to the old object. (Note self is just a variable name like any other and only by convention refers to an object within a class definition.)
The subsequent command self.attribute = new_object.attribute has no effect because self has already become a duplicate label for the new_object.
You could copy all the properties of a new object to the old object. You would end up with two distinct objects with different names and identical properties. A test of equality (a == b) would return false unless you overrode the equality operator for these objects.
To copy all the properties inline you could do something like this:
def replace_object(self, new_object):
self.__dict__ = new_object.__dict__.copy() # just a shallow copy of the attributes
There are very likely better ways to do whatever it is you want to do.

Python: Changing a single object within an array of objects changes all, even in a different array [duplicate]

This question already has an answer here:
Why does this code for initializing a list of lists apparently link the lists together? [duplicate]
(1 answer)
Closed 6 years ago.
I've got a one type of object, data_entry, that has a 2-dimensional array of other objects, time_entry.
Initialization of time_entries the array within data_entry looks like this:
[([time_entry()] * 12) for i in range(5)]
and initialization of the data_entry looks like this:
thing = data_entry()
Now, I have a list of "things", each which contains it's own 2d array of time_entrys.
Each time_entry has a list as one of it's attributes, initialized like so:
attributes = []
I modify attributes by extending it using .extend().
However, the problem I run into when I do this is EVERY single time_entry object in EVERY single data_entry object gets extended.
I know problems like this can arise from improper initialization of objects, so I'm wondering if perhaps my object creations are poor or there is another python quirk I am unaware of.
If you are performing the initialization on the class, it will affect all instances of the class. If that’s the case, this is not a result of it being in a list, but of it being on the class. For example:
#!/usr/bin/python
class BedrockDenizen():
attributes = []
wilma = BedrockDenizen()
fred = BedrockDenizen()
wilma.attributes.extend(['thin', 'smart'])
fred.attributes.extend(['fat', 'stupid'])
print 'Wilma:', wilma.attributes
print 'Fred:', fred.attributes
You will see that both Fred and Wilma are thin, smart, fat, and stupid.
Wilma: ['thin', 'smart', 'fat', 'stupid']
Fred: ['thin', 'smart', 'fat', 'stupid']
One way to fix this is to put the attribute creation into the init method, so that the attribute is per-instance:
class BedrockDenizen():
def __init__(self):
self.attributes = []
With that change, only Wilma is thin and smart, and only Fred is fat and stupid.
Wilma: ['thin', 'smart']
Fred: ['fat', 'stupid']
You may also need to show us more code. #Bakuriu notes that the problem may be that you are only creating one instance, and he may be right. For example, if this is closer to your code:
class BedrockDenizen():
def __init__(self):
self.attributes = []
neighborhood = [([BedrockDenizen()] * 2) for i in range(2)]
flintstones, rubbles = neighborhood
fred, wilma = flintstones
wilma.attributes.extend(['thin', 'smart'])
fred.attributes.extend(['fat', 'stupid'])
print 'Wilma:', wilma.attributes
print 'Fred:', fred.attributes
Then Fred and Wilma will continue to have the same attributes, because they aren’t really separate people. You may wish to use code more like this:
class BedrockDenizen():
def __init__(self):
self.attributes = []
neighborhood = [[BedrockDenizen() for n in range(2)] for i in range(2)]
flintstones, rubbles = neighborhood
fred, wilma = flintstones
wilma.attributes.extend(['thin', 'smart'])
fred.attributes.extend(['fat', 'stupid'])
print 'Wilma:', wilma.attributes
print 'Fred:', fred.attributes
That depends on what your needs are, though, as it seems like an odd way of doing things without more info.
This sounds like your attributes all point to the same list object inside. Then you call extend on the same object every time and modify it.
This is a common issue and discussed at
https://docs.python.org/3/faq/programming.html#how-do-i-create-a-multidimensional-list and Python list append behavior

Why does .append() not work on this list?

I have an object scene which is an instance of class Scene and has a list children which returns:
[<pythreejs.pythreejs.Mesh object at 0x000000002E836A90>, <pythreejs.pythreejs.SurfaceGrid object at 0x000000002DBF9F60>, <pythreejs.pythreejs.Mesh object at 0x000000002E8362E8>, <pythreejs.pythreejs.AmbientLight object at 0x000000002E8366D8>, <pythreejs.pythreejs.DirectionalLight object at 0x000000002E836630>]
If i want to update this list with a point which has type:
<class 'pythreejs.pythreejs.Mesh'>
I need to execute:
scene.children = list(scene.children) + [point]
Usually, I would execute:
scene.children.append(point)
However, while these two approaches both append point, only the first actually updates the list and produce the expected output (that is; voxels on a grid). Why?
The full code can be found here.
I am guessing your issue is due to children being a property (or other descriptor) rather than a simple attribute of the Scene instance you're interacting with. You can get a list of the children, or assign a new list of children to the attribute, but the lists you're dealing with are not really how the class keeps track of its children internally. If you modify the list you get from scene.children, the modifications are not reflected in the class.
One way to test this would be to save the list from scene.children several times in different variables and see if they are all the same list or not. Try:
a = scene.children
b = scene.children
c = scene.children
print(id(a), id(b), id(c))
I suspect you'll get different ids for each list.
Here's a class that demonstrates the same issue you are seeing:
class Test(object):
def __init__(self, values=()):
self._values = list(values)
#property
def values(self):
return list(self._values)
#values.setter
def values(self, new_values):
self._values = list(new_values)
Each time you check the values property, you'll get a new (copied) list.
I don't think there's a fix that is fundamentally different than what you've found to work. You might streamline things a little by by using:
scene.children += [point]
Because of how the += operator in Python works, this extends the list and then reassigns it back to scene.children (a += b is equivalent to a = a.__iadd__(b) if the __iadd__ method exists).
Per this issue, it turns out this is a traitlets issue. Modifying elements of self.children does not trigger an event notification unless a new list is defined.

How do I downcast in python

I have two classes - one which inherits from the other. I want to know how to cast to (or create a new variable of) the sub class. I have searched around a bit and mostly 'downcasting' like this seems to be frowned upon, and there are some slightly dodgy workarounds like setting instance.class - though this doesn't seem like a nice way to go.
eg.
http://www.gossamer-threads.com/lists/python/python/871571
http://code.activestate.com/lists/python-list/311043/
sub question - is downcasting really that bad? If so why?
I have simplified code example below - basically i have some code that creates a Peak object after having done some analysis of x, y data. outside this code I know that the data is 'PSD' data power spectral density - so it has some extra attributes. How do i down cast from Peak, to Psd_Peak?
"""
Two classes
"""
import numpy as np
class Peak(object) :
"""
Object for holding information about a peak
"""
def __init__(self,
index,
xlowerbound = None,
xupperbound = None,
xvalue= None,
yvalue= None
):
self.index = index # peak index is index of x and y value in psd_array
self.xlowerbound = xlowerbound
self.xupperbound = xupperbound
self.xvalue = xvalue
self.yvalue = yvalue
class Psd_Peak(Peak) :
"""
Object for holding information about a peak in psd spectrum
Holds a few other values over and above the Peak object.
"""
def __init__(self,
index,
xlowerbound = None,
xupperbound = None,
xvalue= None,
yvalue= None,
depth = None,
ampest = None
):
super(Psd_Peak, self).__init__(index,
xlowerbound,
xupperbound,
xvalue,
yvalue)
self.depth = depth
self.ampest = ampest
self.depthresidual = None
self.depthrsquared = None
def peakfind(xdata,ydata) :
'''
Does some stuff.... returns a peak.
'''
return Peak(1,
0,
1,
.5,
10)
# Find a peak in the data.
p = peakfind(np.random.rand(10),np.random.rand(10))
# Actually the data i used was PSD -
# so I want to add some more values tot he object
p_psd = ????????????
edit
Thanks for the contributions.... I'm afraid I was feeling rather downcast(geddit?) since the answers thus far seem to suggest I spend time hard coding converters from one class type to another. I have come up with a more automatic way of doing this - basically looping through the attributes of the class and transfering them one to another. how does this smell to people - is it a reasonable thing to do - or does it spell trouble ahead?
def downcast_convert(ancestor, descendent):
"""
automatic downcast conversion.....
(NOTE - not type-safe -
if ancestor isn't a super class of descendent, it may well break)
"""
for name, value in vars(ancestor).iteritems():
#print "setting descendent", name, ": ", value, "ancestor", name
setattr(descendent, name, value)
return descendent
You don't actually "cast" objects in Python. Instead you generally convert them -- take the old object, create a new one, throw the old one away. For this to work, the class of the new object must be designed to take an instance of the old object in its __init__ method and do the appropriate thing (sometimes, if a class can accept more than one kind of object when creating it, it will have alternate constructors for that purpose).
You can indeed change the class of an instance by pointing its __class__ attribute to a different class, but that class may not work properly with the instance. Furthermore, this practice is IMHO a "smell" indicating that you should probably be taking a different approach.
In practice, you almost never need to worry about types in Python. (With obvious exceptions: for example, trying to add two objects. Even in such cases, the checks are as broad as possible; here, Python would check for a numeric type, or a type that can be converted to a number, rather than a specific type.) Thus it rarely matters what the actual class of an object is, as long as it has the attributes and methods that whatever code is using it needs.
See following example. Also, be sure to obey the LSP (Liskov Substitution Principle)
class ToBeCastedObj:
def __init__(self, *args, **kwargs):
pass # whatever you want to state
# original methods
# ...
class CastedObj(ToBeCastedObj):
def __init__(self, *args, **kwargs):
pass # whatever you want to state
#classmethod
def cast(cls, to_be_casted_obj):
casted_obj = cls()
casted_obj.__dict__ = to_be_casted_obj.__dict__
return casted_obj
# new methods you want to add
# ...
This isn't a downcasting problem (IMHO). peekfind() creates a Peak object - it can't be downcast because its not a Psd_Peak object - and later you want to create a Psd_Peak object from it. In something like C++, you'd likely rely on the default copy constructor - but that's not going to work, even in C++, because your Psd_Peak class requires more parameters in its constructor. In any case, python doesn't have a copy constructor, so you end up with the rather verbose (fred=fred, jane=jane) stuff.
A good solution may be to create an object factory and pass the type of Peak object you want to peekfind() and let it create the right one for you.
def peak_factory(peak_type, index, *args, **kw):
"""Create Peak objects
peak_type Type of peak object wanted
(you could list types)
index index
(you could list params for the various types)
"""
# optionally sanity check parameters here
# create object of desired type and return
return peak_type(index, *args, **kw)
def peakfind(peak_type, xdata, ydata, **kw) :
# do some stuff...
return peak_factory(peak_type,
1,
0,
1,
.5,
10,
**kw)
# Find a peak in the data.
p = peakfind(Psd_Peak, np.random.rand(10), np.random.rand(10), depth=111, ampest=222)

Categories

Resources