Automatically initialize multiple instance of class - python

So far I mostly used Python for data analysis but for some time try to implement stuff. Right now I'm trying to implement a toxicokinetic-toxicodynamic model for a fish to analyse the effect of chemicals on them.
So given the following code:
import numpy as np
class fish():
def __init__(self):
self.resistance_threshold = np.random.normal(0,1)
My question is now, say I would like to initialize multiple instances of the fishclass (say 1000 fish), each with a different resistance to a chemical in order to model an agent-based population. How could one achieve this automatically?
I was wondering if there is something as using for example an index as part of the variable name, e.g.:
for fishid in range(0,1000):
fishfishid = fish() # use here the value of fishid to become the variables name. E.g. fish1, fish2, fish3, ..., fish999
Now even if there is a possibility to do this in Python, I always have the feeling, that implementing those 1000 instances is kinda bad practice. And was wondering if there is like an OOP-Python approach. Such as e.g. setting up a class "population" which initializes it within its own __init__function, but how would I assign the fish without initializing them first?
Any tipps, pointers or links would be greatly appreciated.

You can create a class FishPopulation and then store there all the Fish you need based on the size argument. For example, something like this would work:
import numpy as np
class Fish:
def __init__(self):
self.resistance_threshold = np.random.normal(0, 1)
class FishPopulation:
def __init__(self, size=1000):
self.size = size
self.fishes = [Fish() for _ in range(size)]
You can iterate over it like this:
fish_population = FishPopulation(size=10)
for fish in fish_population.fishes:
print(fish.resistance_threshold)
>>>
-0.9658927669391391
-0.5934917229482478
0.8827336199040103
-1.5729644992077412
-0.7682070400307331
1.464407499255235
0.7724449293785645
-0.7296586180041732
-1.1989783570280217
0.15716170041128566
And you can access their indexes like this:
print(fish_population.fishes[0].resistance_threshold)
>>> -0.9658927669391391

Related

How to get the same class variable of a list of class instances of the same class?

I have a class which has some class variables, methods, etc. Let's call it Cell.
class Cell:
def __init__(self):
self.status = 0
...
I have a list of different instances of this class.
grid = [Cell.Cell() for i in range(x_size*y_size)]
Is it possible to get the upper shown status variable of each of the instances stored in grid in a vectorized manner without looping through the elements of the list?
Not in vanilla Python.
statuses = [x.status for x in grid]
If you are looking for something that abstracts away the explicit iteration, or even just the for keyword, perhaps you'd prefer
from operator import attrgetter
statuses = list(map(attrgetter('status'), grid))
?

Python inheritance for lists of instances

I have a python object, with many attributes and functions (dummy example bellow):
class molecule:
def __init__(self, atoms, coords):
self.atoms=np.copy(atoms)
self.coords=np.copy(coords)
def shift(self,r):
self.coords=self.coords+r
I would like to generate preferably a numpy array (or a list) of these objects and to obtain its properties without always looping over the array. At the moment I create a list of molecule objects (mols) by a loop and check its attributes by loops eg:
atomList=[mol.atoms for mol in mols]
but I would prefer to obtain it as:
atomList=mols.atoms
Is there an automatic way to obtain such an array/list class without manually defining the molList class and manually add its attributes, functions etc?
You can use a class_variable. The difference between class variables and instance variables can be found here:
https://medium.com/python-features/class-vs-instance-variables-8d452e9abcbd#:~:text=Class%20variables%20are%20shared%20across,surprising%20behaviour%20in%20our%20code.
For your example, something like this ought to work:
class molecule:
atomList = [] # class variable
def __init__(self, atoms, coords):
self.atoms=np.copy(atoms) # instance variable
self.coords=np.copy(coords)
molecule.atomList.append(atoms) # update the class variable with each new instance of the class
def shift(self,r):
self.coords=self.coords+r
Then in your code, you can just do atomlist = molecule.atomList

Python Beginners: Creating dynamic class objects with dynamic attributes with loops

I'm trying to create some simple objects that are defined dynamically through a class - to allow me to rapidly iterate through the creation of all possibilities of these objects.
class NSObjects:
def __init__(self, shape, position, shading):
self.shape = shape
self.position = position
self.shading = shading
def __str__(self):
return '{} - {} - {}'.format(self.shape(), self.position(), self.shading())
def NSGenerator_1():
for i in range (0,3):
obj_1_i = NSObjects(shape_init_top + i, posn_init_top+i, shading_init_top+i)
for i in range (3,6):
obj_1_i = NSObjects(shape_init_mid + i, posn_init_mid+i, shading_init_mid+i)
for i in range (6,9):
obj_1_i = NSObjects(shape_init_mid + i, posn_init_mid+i, shading_init_mid+i)
NSGenerator_1()
print(obj_1_2)
At the moment it is telling me that obj_1_2 doesn't exist. For the purpose of this you can assume that I have defined all the init variables to start at 0, 1 or 2 elsewhere in the code. I am basically trying to create a series of objects which will have properties as defined by a mathematical formula.
Thanks in advance for any help you can provide (I only started coding a few weeks ago so this might be a very silly question!)
You only ever assigned to obj_1_i, not obj_1_2, and it was local to the function. There is no way for Python to tell that the _i was meant as a separate variable instead of part of the longer variable name. For a quick fix, try replacing the
obj_1_i = parts with globals()[f'obj_1_{i}'] =.
But rolling numeric indexes into the variable names like that (_1_2) is a code smell. A better design is to actually use them as indexes to a data structure, like a list or dict.
For example, define
obj = {} at the top level (outside of any class or function).
Then you can replace obj_1_2 everywhere with obj[1, 2], etc. If you wrote them that way,obj[1, i] would work as you expect inside those for loops.

How to sort by the attributes of classes in Python?

I have two classes set up (with some other attributes that are irrelevant).
class Alcohol():
def __init__(FunctionalGroup):
FunctionalGroup.Naming = ["hydroxy", "ol"]
class Halogenoalkane():
def __init__(FunctionalGroup):
FunctionalGroup.Naming = ["chloro", "bromo", "iodo"]
I want to be able to sort a given string such as ethanol or 2-chloromethane into one of these and create an instance based on which class the name fits into. For example:
>>> Name: Ethanol
This is an alcohol.
I am looking for a way to iterate over the FunctionalGroup.Naming list in each class and check if any of them are contained in the string.
What is the best approach to doing this or alternative data structures?
(Sorry if you don't like chemistry I'm just trying to make revising it more interesting)
I am not sure if this is the cleanest way of doing it, and I removed the instance variables and made a constant list in each class instead. This way it is easier to reference, and the list seems like a constant anyhow:
class Alcohol():
Naming = ["hydroxy", "ol"]
def __init__(self):
print ' ---> Alcohol'
class Halogenoalkane():
Naming = ["chloro", "bromo", "iodo"]
def __init__(self):
print ' ----> Halogen'
str = 'hydroxy'
classes = [Alcohol, Halogenoalkane]
chosen_class = object
for cl in classes:
if str in cl.Naming:
chosen_class = cl
print '{} is an:'.format(str)
obj = chosen_class() # instantiate the class
output:
hydroxy is an:
---> Alcohol

Python: dynamically create methods based on other classes'

I've looked for quite a while but couldn't find a proper answer to my question:
I have a class containing methods which operate on arrays and I want dynamically create methods with a similar name in another class with a modified output.
I've got something like this so far, can anyone guide me ?
Thanks
Class A():
def__init__(self,array):
self.data = array
def method1(self,*args):
newarray = whatever(self.data,*args)
return newarray
def method2(self,*args):
newarray = whatever2(self.data,*args)
return newarray
I want to be able to use those methods to generate new ones in a more complex class, say:
class B(C): #inherits from C
def __init__(self,[arg1,array]):
#initialize from parent class
C.__init__(self,[arg1,array])
#create new methods for this class using same name
methodnames = [element for element in dir(A) if element[0] != '_']
for methodname in methodnames:
##following works but this is not the output I want here
#self.__dict__[methodname] = getattr(A(array),methodname)
#following doesn't work... at least not as I expect it to
#case1
#self.__dict__[methodname] = [arg1,getattr(A(array),methodname)]
#case2
self.__dict__[methodname] = list([arg1,getattr(A(array),methodname)])
a = array
#following returns a list of [arg1, method] but what I really want is [arg1,newarray]
C([arg1,array]).method1(*args)
OK, so let's try to be clearer:
Class A contains filters, takes an array and applies filter as method, returns filtered data.
Class filters()
def__init__(self,array):
self.data = array
def filter1(self,*args):
newarray = median(self.data,*args)
return newarray
def filter2(self,*args):
newarray = gaussian(self.data,*args)
return newarray
...
In another module, I have class SpecialData, which operates on a list of x,y data (where x and y are iterables, i.e. lists, arrays...). So something like
Class SpecialData():
def __init__(self,[x,y]):
self.data = [x,y]
def power(self,power):
ypow = self.data[1]**power
return [x,pow]
def porod(self):
return [x**4,x**4*y]
....
Now, what I want is to add the filter methods contained in class filters to class SpecialData.
I could, of course do this by re-coding all filters with proper format for SpecialClass. but what I really want, is that each time a new filter is added to class filters, to make it available at runtime in class SpecialData without having to re-hard code the new filter.
So, not being very clever, I tried to read the list of available filters in class filters by:
import filters
filternames = [element for element in dir(filters) if element[0] != '_']
for fitlername in filternames:
generate_filters_in_class_SpecialClass
How do I do this properly ?
I found a number of posts related to this, some using super(), others using SpecialData.dict or even setattr. Since the 2nd seemed more understandable to me, I focused on this one and came up with:
import filters
Class SpecialData():
def __init__(self,[x,y]):
self.data = [x,y]
filternames = [element for element in dir(filters) if element[0] != '_']
for fitlername in filternames:
self.__dict__[fitlername ] = [self.data[0],getattr(filters(self.data[1]),fitlername)]
Of course, this doesn't work, because the list is not callable. If I change the last line to :
self.dict[fitlername ] = list([self.data[0],getattr(filters(self.data[1]),fitlername)])
it returns the method as the 2nd element, rather than the result.
Note that the following works, but this is not what I want...
self.dict[fitlername ] = getattr(filters(self.data[1]),fitlername)
Hope this is clearer now...
I think you are trying to make an advanced use of Python without using/knowing its advanced features, like you are borrowing techniques from another language.
This is not a criticism, but you should have a look on Python tutorial, Python introspection or metaclasses.
I think that if you just complete your knowledge on Python functions you will be easily able to solve your problem in a much simpler way.
Rather than generating a proposed solution, you should make it clearer what you are trying to achieve. Class A is a clear example of the starting point; please post an example of your desired ending point, e.g.
Class B():
def__init__(self,array):
self.data = array
def method1(self,*args):
newarray = ComplexWhatever(self.data,*args)
return newarray
def method2(self,*args):
newarray = EvenBiggerWhatever2(self.data,*args)
return newarray
a = A(input_array)
b = B(input_array)
print(a.method1(args))
print(b.method1(args))
What isn't clear is how you want to "dynamically generate" the new function "ComplexWhatever()" instead of writing the function by hand.

Categories