numpy array of objects - python
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.
Related
How would I interact with an object with no name?
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]
How to create different objects with a constructor list
I have a list which has different constructors of different classes. But the constructors always return the same object because they have the same memory direction. I have something like this: l=[class1(),class2(),class3()] l2 = [] If I try to create different objects with it, it returns the same object with the same memory direction. I'm doing this: for i in range(50): obj = l[random] l2.append(obj) l2 has 50 objects but all the objects of the first class are the same and they have the same memory direction. Same happens with the other classes. I would like to have 50 differents objects.
You must call the constructor of the class each time you want to add an instance to the list. For this I would suggest that you use a list of classes rather than a list of object instances (otherwise you're just adding the same references to these 3 instances to the list) l=[class1,class2,class3] for _ in range(50): cls = l[random] l2.append(cls())
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]
numpy.r_ is not a function. What is it?
According to the numpy/scipy doc on numpy.r_ here, it is "not a function, so takes no parameters". If it is not a function, what is the proper term for "functions" such as numpy.r_?
I would argue that for all purposes r_ is a function, but one implemented by a clever hack using different syntax. Mike already explained how r_ is in reality not a function, but a class instance of RClass, which has __getitem__ implemented, so that you can use it as r_[1]. The cosmetic difference is that you use square brackets instead of curved ones, so you are not doing a function call, but you are actually indexing the object. Although this is technically true, for all purposes, it works just like a function call, but one that allows some extra syntax not allowed by a normal function. The motivation for creating r_ probably comes from Matlab's syntax, which allows to construct arrays in a very compact way, like x = [1:10, 15, 20:10:100]. To achieve the same in numpy, you would have to do x = np.hstack((np.arange(1,11), 15, np.arange(20,110,10))). Using colons to create ranges is not allowed in python, but they do exist in the form of the slice notation to index into a list, like L[3:5], and even A[2:10, 20:30] for multi-dimensional arrays. Under the hood, these index notation gets transformed to a call to the __getitem__ method of the object, where the colon notation gets transformed into a slice object: In [13]: class C(object): ...: def __getitem__(self, x): ...: print x In [14]: c = C() In [15]: c[1:11, 15, 20:110:10] (slice(1, 11, None), 15, slice(20, 110, 10)) The r_ object 'abuses' this fact to create a 'function' that accepts slice notation, which also does some additional things like concatenating everything together and returning the result, so that you can write x = np.r_[1:11, 15, 20:110:10]. The "Not a function, so takes no parameters" in the documentation is slightly misleading ...
It's a class instance (aka an object): In [2]: numpy.r_ Out[2]: <numpy.lib.index_tricks.RClass at 0x1923710> A class is a construct which is used to define a distinct type - as such a class allows instances of itself. Each instance can have properties (member/instance variables and methods). One of the methods a class can have is the __getitem__ method, this is called whenever you append [something,something...something] to the name of the instance. In the case of the numpy.r_ instance the method returns a numpy array. Take the following class for example: class myClass(object) def __getitem__(self,i) return i*2 Look at these outputs for the above class: In [1]: a = myClass() In [2]: a[3] Out[2]: 6 In [3]: a[3,4] Out[3]: (3, 4, 3, 4) I am calling the __getitem__ method of myClass (via the [] parentheses) and the __getitem__ method is returning (the contents of a list * 2 in this case)- it is not the class/instance behaving as a function - it is the __getitem__ function of the myClass instance which is being called. On a final note, you will notice that to instantiate myClass I had to do a = myClass() whereas to get an instance of RClass you use numpy.r_ This is because numpy instantiates RClass and binds it to the name numpy.r_ itself. This is the relevant line in the numpy source code. In my opinion this is rather ugly and confusing!
instance of object versus object value python
I having difficulties to understand the instance of an object in a list. How to save the value of an object into a list without saving the instance? This is not possible isnt it? The colde below works but i would like to avoid to use .value as i might have several parameters.. not sure if i am clear enough.. class BougieBuffer: def __init__(self): self.bougiebuffer=deque(maxlen = 10000) self.maximum = Maximum() def update(self,bougie): self.maximum.value = random.randint(-1,1) bougie.maximum.value = self.maximum.value self.bougiebuffer.append(bougie) print len(self.bougiebuffer) for i in range (len(self.bougiebuffer),0,-1): print self.bougiebuffer[i-1].prixFermeture, self.bougiebuffer[i-1].maximum.value I would have wrote naturally something like below but obviously this is not working and it returns the same value for all bougie.maximum = self.maximum
You want to create a copy of the Maximum() instance to assign to the bougie.maximum attribute; use either copy.copy or copy.deepcopy: from copy import deepcopy bougie.maximum = deepcopy(self.maximum) You'll need deepcopy if there are any attributes of Maximum that are mutable; a list, dict, set or another custom class instance are all mutable, but things like integers and strings are not.