How would I interact with an object with no name? - python

How would I go about interacting with an instance of a class that isn't created directly by me, similar to below:
Matrix = [[MyClass() for x in range(5)] for y in range(5)]
I'm attempting to read some information from each instance at each index of the Matrix, such a number like anyNum = 9. How would I go about doing this since I didn't assign a name to each instance?

You have a list of lists, so simply iterate:
for my_list in Matrix:
for obj in my_list:
do_something(obj)
Here, obj is an instance of MyClass.

You can access the object via indexing:
anyNum = Matrix[0][0]

Related

Can you use list.index for a list of objects?

If I have a list of objects made from classes, is there a way to get the index of a particular object in that list?
I've tried using list.index like this:
obj_list = [object1(), object2()]
object1_index = obj_list.index(object1())
return object1_index
But this just returns a ValueError, saying that object1() is not in list, even though it is.
object1 is a constructor; each time you say object1() you're constructing a new object of class object1. Hence the object1() in your index() call does not refer to the same object as the one in obj_list.
You could do something like:
next(i for i, x in enumerate(obj_list) if isinstance(x, object1))
to find the index of the first object in obj_list that is an instance of object1.

Python Appending objects to a list

Beginner with Python, need some help to understand how to manage list of objects.
I built a list of simple objects representing a coordinate
class Point:
def __init__(self, x, y):
self.x=0
self.y=0
I create a empty list to store different points :
combs = []
point = Point(0, 0)
then I build different points using point and ever ytime appending to the list combs
For instance:
point.x=2
point.y=2
combs.append(point)
point.x=4
point.y=4
combs.append(point)
I expect that combs is something like [.. 2,2 4,4] on the contrary it's [....4,4 4,4].
It means that every time I change the instance of a point, I change all the points stored in the list with the latest value.
How can I do this?
The thing is when you're trying to change the value of x and y , you're expecting to have a new object (like a new x and y with different values) but you aren't. What happens is I think is whenever you set point.x =4 and point.y = 4 is you're just changing the attribute x and y in your class
take a look at this link. This helped me a lot, I encountered that kind of problem or should I say similar of yours
I suggest using the copy package
https://www.programiz.com/python-programming/shallow-deep-copy
You are appending the same variable to the combine. You need to create a new Point object and initialize it with the new values.
combine = []
p = Point(2,2)
combine.append(p)
p = Point(4,4)
combine.append(p)
This is because Python is using reference count for the garbage collection.
In your example you create point which increments the ref count.
Then you pass it to the list which increment the count. Then change the value and pass the same variable. Which increments the count once again.
Think of it more like passing a reference or memory pointer of the point variable to the list. You gave the list twice the same pointer.
So you need to create different variables or make a deep copies https://docs.python.org/3.8/library/copy.html
Custom classes, unless they are built on an immutable type, are mutable. This means that you have appended a reference to your list, and that changing the value of the references will change every instance of the reference. Consider this:
class Test():
def __init__(self, a):
self.a = a
>>> t1 = Test(1)
>>> t1.a
1
>>> t2 = t1
>>> t2.a
1
>>> t2.a = 2
>>> t2.a
2
>>> t1.a
2
See how changing t2 also changed t1?
So you have a few options. You could create new points instead of reusing old ones, you could use a copy() method, or you could write a method into your Point class that exports something immutable, like a tuple of the (x, y) values and append that to your list, instead of appending the entire object to your list.
You are working with only a single Point. Construct a second one. See commented line below.
point = Point(0, 0)
point.x=2
point.y=2
combs.append(point)
point = Point(0, 0) # add this
point.x=4
point.y=4
combs.append(point)
By the way, your __init__ ignores its parameters -- throws them away. A better version is below. We assign self.x=x to make use of the parameter. (Likewise y).
def __init__(self, x, y):
self.x=x
self.y=x
You need to pass one value into point
How to add an item to your list
combs = []
point = 1 # For example
combs.append(point)
Use command lines to study
Try to use BASH or CMD... Use command lines... They will have instant feedback of your code
Good place to find basic stuff
Try to see the examples on w3scholl. It is a great place. Here is the link for W3Scholl - Python - List
Understand basics first
Before you jump into classes, try to understand lists very well! You will learn more and build a solid knowledge if you take a step by step growing! Keep pushing!!!

Creating copy of arbitrary object in Python and calling constructor

I have classes A, B, and C which inherit from Foo. Their __init__ methods all do different things, but have a similar signature: they all take a single parameter i in __init__. Some number of instances of these classes are in a list l, and all mixed together. In l, all the objects have i=1.
I need to go through l, and for every object I see, I need to create the same object, but instantiated with i=2 instead of i=1.
How do I do this?
I tried this:
l2 = []
for obj in l:
obj_2 = type (obj).__init__(2)
l2.append(obj_2)
But it didn't work.
I'm not sure what you mean by "create the same object". If you mean "create a brand-new distinct object of the same type", then try this:
obj_2 = type (obj)(2)
Your code, rewritten as a list comprehension:
l2 = [type(obj)(2) for obj in l]

Creating instances in a loop with different variables

I'm tring to create class instances in a loop. All instances need to be assinged to a different variable. These variables can be a sequence of letters like [a,b,c].
class MyClass(object):
pass
for i in something:
#create an instance
If the loop turns 3 times, I want the loop make something like that:
a = MyClass()
b = MyClass()
c = MyClass()
Is there a way to do that?
Using independent variable names this way is a bit odd; using either a dict or a list, as shown above, seems better.
Splitting it down the middle, how about
a,b,c = (MyClass() for _ in range(3))
You can do this using exec. See also Modifying locals in Python
>>> class Foo(object): pass
...
>>> for name in "abc":
... exec "{0} = Foo()".format(name)
...
>>> a
<__main__.Foo object at 0x10046a310>
>>> b
<__main__.Foo object at 0x10046a390>
>>> c
<__main__.Foo object at 0x10046a3d0>
you could have a list of the names you want to name the objects and then in the loop you add the names to the global namespace as you create and name the objects.
list_of_names = ['a', 'b', 'c', 'd']
for name in list_of_names:
globals()[name] = your_object()
People here will definitely say this is a bad way to code without giving any cogent reason but it directly solves your problem without any further list or dict.
You could create a list holding all the instances. For instance:
instances = [MyClass() for i in range(0, N)]
Not easily, it's generally a bad idea to do something like that; someone else can elaborate why, because it's not really clear to me.
What you might do instead is
class MyClass(object):
pass
list_of_insts = []
for i in something:
#create an instance
lists_of_insts.append(MyClass())
Then you can refer to each positionally in that list: lists_of_insts[0]. You could also assign them to a dict instead of a list for easier access.
How about storing those variables in a dict? Like
variables = {}
varnames = ['a', 'b', 'c']
for i, something in enumerate(things):
variables[varnames[i]] = MyClass()

numpy array of objects

I'm trying to implement a simulation for a lattice model (lattice boltzmann) in Python. Each site of the lattice has a number of properties, and interact with neighboring sites according to certain rules. I figured that it might be clever to make a class with all the properties and make a grid of instances of that class. (As I'm inexperienced with Python, this might not be a good idea at all, so feel free to comment on my approach.)
Here is a toy example of what I'm doing
class site:
def __init__(self,a,...):
self.a = a
.... other properties ...
def set_a(self, new_a):
self.a = new_a
Now I want to deal with a 2D/3D lattice (grid) of such sites so I tried to do the following (here is a 2D 3x3 grid as an example, but in simulation I would need the order of >1000x1000X1000)
lattice = np.empty( (3,3), dtype=object)
lattice[:,:] = site(3)
Now, the problem is that each lattice point refer to the same instance, for example
lattice[0,0].set_a(5)
will also set the value of lattice[0,2].a to 5. This behavior is unwanted. To avoid the problem i can loop over each grid point and assign the objects element by element, like
for i in range(3):
for j in range(3):
lattice[i,j] = site(a)
But is there a better way (not involving the loops) to assign objects to a multidimensional array?
Thanks
You can vectorize the class's __init__ function:
import numpy as np
class Site:
def __init__(self, a):
self.a = a
def set_a(self, new_a):
self.a = new_a
vSite = np.vectorize(Site)
init_arry = np.arange(9).reshape((3,3))
lattice = np.empty((3,3), dtype=object)
lattice[:,:] = vSite(init_arry)
This may look cleaner but has no performance advantage over your looping solution. The list comprehension answers create an intermediate python list which would cause a performance hit.
The missing piece for you is that Python treats everything as a reference. (There are some "immutable" objects, strings and numbers and tuples, that are treated more like values.) When you do
lattice[:,:] = site(3)
you are saying "Python: make a new object site, and tell every element of lattice to point to that object." To see that this is really the case, print the array to see that the memory addresses of the objects are all the same:
array([[<__main__.Site object at 0x1029d5610>,
<__main__.Site object at 0x1029d5610>,
<__main__.Site object at 0x1029d5610>],
[<__main__.Site object at 0x1029d5610>,
<__main__.Site object at 0x1029d5610>,
<__main__.Site object at 0x1029d5610>],
[<__main__.Site object at 0x1029d5610>,
<__main__.Site object at 0x1029d5610>,
<__main__.Site object at 0x1029d5610>]], dtype=object)
The loop way is one correct way to do it. With numpy arrays, that may be your best option; with Python lists, you could also use a list comprehension:
lattice = [ [Site(i + j) for i in range(3)] for j in range(3) ]
You can use a list comprehension with the numpy.array construction:
lattice = np.array( [ [Site(i + j) for i in range(3)] for j in range(3) ],
dtype=object)
Now when you print lattice, it's
array([[<__main__.Site object at 0x1029d53d0>,
<__main__.Site object at 0x1029d50d0>,
<__main__.Site object at 0x1029d5390>],
[<__main__.Site object at 0x1029d5750>,
<__main__.Site object at 0x1029d57d0>,
<__main__.Site object at 0x1029d5990>],
[<__main__.Site object at 0x1029d59d0>,
<__main__.Site object at 0x1029d5a10>,
<__main__.Site object at 0x1029d5a50>]], dtype=object)
so you can see that every object in there is unique.
You should also note that "setter" and "getter" methods (e.g., set_a) are un-Pythonic. It's better to set and get attributes directly, and then use the #property decorator if you REALLY need to prevent write access to an attribute.
Also note that it's standard for Python classes to be written using CamelCase, not lowercase.
I don't know about better, but as an alternative to an explicit set of loops, you could write
lattice = np.empty( (3,3), dtype=object)
lattice.flat = [site(3) for _ in lattice.flat]
which should work whatever the shape of the lattice.

Categories