Python create objects in for loop - python

I have a class to assign some parameters:
class body:
def __init__(self, name, number, L):
self.name = name
self.number = number
self.L = L
And I would like to assign these parameters to 10 almost equal bodies like:
for i in range(0, 10):
body[i].name = "test_name"
body[i].number = i
body[i].L = 1.
And to be able to change, lets say, the parameter L of body 3 from 1 to 2:
body[3].L = 2
Thank you very much for the help.

Note that body is a class. Using body[i] suggests you may be intending to use body as a list. If you want to create a list of 10 instances of body, do not name the list body as well. You could instead name the list bodies and define it with a list comprehension:
bodies = [body("test_name", i, 1.) for i in range(0, 10)]
bodies[3].L = 2
By the way, PEP8 Style Guide recommends all classes follow the CapWords convention. So to conform with the convention, body should be Body. By following this convention, everyone reading your code will understand immediately what is a class and what is not.

Related

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 create several classes in a loop?

I have a script, where I have to define several classes:
class track1:
number = 0
min_class = 0
members = []
class track2:
number = 0
min_class = 0
members = []
class track3:
number = 0
min_class = 0
members = []
And so on...
Later I change in some classes the values. For example: min_class will be 10 in the classes 2,5 and 6. Or the members list will contain different members in every different track.
But sometimes I have to define four classes, sometimes 16.
So my question is: Is there a way, to define classes in Python in a loop?
for i in range(x):
#define class track i
Use the type function to dynamically create classes.
track = []
for i in range(x):
track.append(type('track%d' % i, (), {'number': 0, 'min_class': 0, 'members': []}))
Yes - this can be done. The only strange part is to force the classes created dynamically to actually have a name in the module - although it can be done by writting to the globals() dictionary.
Anyway, what creates a class dynamically in Python is a call to type: the class of which classes are instances.
So, a simple way there, if the classes have all the same body, is to create a Base class for all of then, and then you could do at module level:
class Base:
attributes = 0
...
for i in range(16):
globals()[f'class{i}'] = type(f'class{i}', (Base,), {})
Depending on what how you intend your code to be read, if the name is the only issue, you could just write a for loop, and declare the class inside it as well, just taking care of the name - creating a class with a class kewyord block and using type are equivalent (but for static analysis tools, like autocompleters used by IDEs - this stuff will get lost eitherway)
for i in range(16):
class Base:
attributes = []
...
Base.__name__ = f"class{i}"
globals()[f"class{i}"] = Base
However, as I stated in the very beggining, it is not usual to dynamically create variables in Python code - and by variables here, I mean anything with a static name , including functions and classes - if you are typing the hardcoded name of such a class in another ".py" file, it should be typed hardcoded here.
So, if your classes are to be used dymically, let's say some other code have to select an specific class of these based on some other data, it is not conveninent they are bound to the module as "class1, class2", etc... rather, they should be part of another data structures, such as a list or dictionary - let's suppose you want one such class depending on a "product category" some other part of the code would have - You could just as well create a dicionary whose keys are product categories, and the values the classes.
Or, to keep things simple, let's just do a list:
myclasses = []
for i in range(16):
myclasses.append(type(f"class{i}", (Base,), {})
(The form with the class body is valid as well, the only difference is that you don't assign your generated classes to the dict in globals() , and rather, to another data structure.
Your class definitions are identical.
Why not have one class definition:
class track:
number = 0
min_class = 0
members = []
and then create as many instances as you need?
L = []
for i in range(x):
L.append(track())
Now, you possibly want the class members to be instance members, so need to use self appropriately.
You've created your class trackX multiple times, however you need to create instances of one class:
class Track:
number = 0
min_class = 0
members = []
def __init__(number, min_class, members):
self.number = number
self.min_class = min_class
self.members = members
Then in your loop you want to create instances of your class:
for i in range(x):
track = Track(number, min_class, members)
If you want a list of tracks just append this track to your list of tracks:
tracks = []
for i in range(x):
track = Track(number, min_class, members)
tracks.append(track)
A large part of design and programming is avoiding or removing duplication.
This is what you are trying to do, so that's a good start.
However, the only thing that varies is the name of the class, which seems a strange thing to need.
When you instantiate the classes there will essentially be no other difference between the object types.
In design you want to encapsulate what stays the same (a class or algorithm for example), and parameterize it with what varies (data).
I'd advise you to parameterize the object constructor with the track name:
class Track:
def init(name, number, min_class, members):
self.name = name
self.number = number
self.min_class = min_class
self.members = members

How can I make a list or a dictionary with each element being a class instance?

I don't think the design below is a good idea. However, this is for an exam, and they require a list (technically an array, because Cambridge is very biased in favour of VB and Pascal) where each element is of a certain class type. Here is the class diagram:
Also, the board demands that all attributes be private even though -- based on my rudimentary knowledge of Python -- "private" attributes aren't really a thing. Here is the code exhibiting the problem:
class Question():
def __init__(self):
self.__QuestionID = ""
self.__QuestionText = ""
self.__Answer = ""
self.__Mark = ""
self.__Topic = ""
class Test():
def __init__(self):
self.__TestdID = ""
self.__Questions = []
self.__NumOfQs = None
self.__MaxMarks = 0
self.__Level = None
self.__DateSet = None
def DesignTest(self):
self.__NumOfQs = int(self.__NumOfQs)
self.__Questions = [Question for x in range(self.__NumOfQs)] `
for i in self.__Questions:
i = Question()
i.SetQuestion()
The last four lines are my problem area: I don't know how to implement the list of instance objects.
You are correct: Python doesn't have explicit private and public attributes.
I'm not sure what you're trying to do in the final for loop: you already have a list, except that you filled it with the class object itself, instead of instances. Try this:
self.__Questions = [Question() for x in range(self.__NumOfQs)]
Now you have a list of instance objects. If you need to call SetQuestion on each, you already have it set up in that loop:
for i in self.__Questions:
i.SetQuestion()
You can just initiate them in the list itself.
use
self.__Questions = [Question() for x in range(self.__NumOfQs)]
then also try :
for i in self.__Questions:
i.SetQuestion()
The line
self.__Questions = [Question for x in range(self.__NumOfQs)]
will create a list with uninstanciated objects, as many as you have in self.__NumOfQs. If you want to create instanciated objects, just do
self.__Questions = [Question() for x in range(self.__NumOfQs)]
instead and skip the last lines, or skip the line above and do
for i in self.__NumOfQs:
self.__Questions.append(Question())
If i understand correctly you just want a list containing instances of the Question class? You can simply instantiate them in the list generation.
self.__Questions = [Question() for x in range(self.__NumOfQs)]
You can then finish with:
for i in self.__Questions:
i.SetQuestion()

What is the proper way to share a list between class instances?

I have this example code
my_list = ["a","b","c","d"]
class A:
def __repr__(self):
return ', '.join(my_list)
def add(self, num):
my_list.append(num)
class_list = []
for x in range(5):
class_list.append(A())
class_list[x].add("class_%s" % (x))
print class_list[x]
The non-example code of mine is more complicated, but the idea is that I have multiple instances of the classes off doing a "thing". The global my_list is utilized across all instances. When certain logic is met within a class, that instance will modify the list. The rest of the classes will utilize that list to perform their logic as well. Any instance can add to the list, and all instances should be able to utilize the updated value.
Now in this example, the my_list is shared, but is this the correct way to do it?
A class attribute is usually better than a global, because then they're just sharing it with each other, rather than with everyone in the world.
To do that, move the my_list = ["a","b","c","d"] line under the class A:, and change every reference to my_list to self.my_list or A.my_list:
class A(object):
shared_list = []
def add(self, num):
self.my_list.append(num)
However, an instance attribute is often even better. If you assign the same list to a bunch of different variables, it's still just one list; changing it affects all those variables. So, you can do something like this:
class A(object):
def __init__(self, shared_list):
self.shared_list = shared_list
def add(self, num):
self.shared_list.append(num)
Now it's up to the code that uses the A objects to decide whether to give them all the same list. You can even create 20 instances that share one list, and 10 that share a different one:
list1 = []
group1 = [A(list1) for _ in range(20)
list2 = []
group2 = [A(list2) for _ in range(10)
The question is whether the caller, or the A class, or nobody at all is the one who should be making the decision of how "shared" the list is. The answer is different for different applications, so it's hard to give an answer for an abstract example with names like A and my_list.

Python reverse introspection

Lets suppose this example: Two siblings classes where one loads the other class as a new attribute and then i wish to use this attribute from the main class inside the sibling.
a = 2
class AN(object):
def __init__(self,a):
self.aplus = a + 2
self.BECls = BE(a)
class BE(object):
def __init__(self,a):
print a
def get_aplus(self):
????
c = AN(a)
and i'd like to do:
c.BECls.get_aplus()
and this shall return something like self.self.aplus (metaphorically), that would be 4
Resuming: get aplus attribute from AN inside BE class, without declaring as arguments, but doing a "Reverse introspection", if it possible, considering the 'a' variable must be already loaded trough AN.
Sorry if I not made myself clear but I've tried to simplify what is happening with my real code.
I guess the problem may be the technique i'm using on the classes. But not sure what or how make it better.
Thanks
OP's question:
get aplus attribute from AN inside BE class, without declaring as
arguments, but doing a "Reverse introspection", if it possible,
considering the 'a' variable must be already loaded trough AN.
The closest thing we have to "reverse introspection" is a search through gc.getreferrers().
That said, it would be better to simply make the relationship explicit
class AN(object):
def __init__(self,a):
self.aplus = a + 2
self.BECls = BE(self, a)
class BE(object):
def __init__(self, an_obj, a):
self.an_obj = an_obj
print a
def get_aplus(self):
return self.an_obj.aplus
if __name__ == '__main__':
a = 2
c = AN(a)
print c.BECls.get_aplus() # this returns 4

Categories