How to create several classes in a loop? - python

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

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 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

Need help on creating object with class

I need help on creating an object (a sequence of numbers) in respect to some parameters of a class. Lets say I typed in to the Python IDLE shell:
SuperLotto = make_lottery_set_type('SuperLotto', 6, (1,50))
#means user can create a 'SuperLotto' with 6 numbers in range of 1 to 50
It would make 'SuperLotto' as a new class instance of a class called 'LotteryGameType'.
This is using the code so far:
class LotterySetError(Exception):
pass
def make_lottery_set_type(name:str, size:int, minmax:tuple):
if minmax[0] > minmax[1]:
raise LotterySetError('Illegal range for tuple')
else:
name = LotteryGameType(name, size, minmax[0], minmax[1])
return name
class LotteryGameType:
def __init__(self, name, set_size, min_set_number, max_set_number):
self.name = name
self.set_size = set_size
self.min_set_number = min_set_number
self.max_set_number = max_set_number
I want to be able to create a sequence of numbers and storing it for later use so I can use it with things like overload operators (e.g. eq and ne).
I want to be able to type into the Python IDLE shell:
SuperLotto([3, 4, 19, 23, 46, 27])
This would create an object under the parameters of SuperLotto, if not under parameters of 'SuperLotto' (say more than 6 numbers), it would raise an error. Any approach would be fine. Does anyone have any ideas on how to approach this?
It sounds like what you want is for make_lottery_set_type to return a new class, presumably one that's a subclass of LotteryGameType, rather than returning an instance of that type.
This is actually pretty easy to do in Python. Class definitions are just normal code, that you can run anywhere, even in the middle of a function. And they have access to the local environment while they're running. And classes themselves are "first-class values", meaning you can pass them around and return them from functions. So:
def make_lottery_set_type(name:str, size:int, minmax:tuple):
if minmax[0] > minmax[1]:
raise LotterySetError('Illegal range for tuple')
else:
class NewLotteryGameType(LotteryGameType):
def __init__(self, numbers):
super().__init__(name, size, minmax[0], minmax[1])
self.numbers = numbers
return NewLotteryGameType
If you want to add other methods, that's the same as adding methods to any other class. For example:
def make_lottery_set_type(name:str, size:int, minmax:tuple):
if minmax[0] > minmax[1]:
raise LotterySetError('Illegal range for tuple')
else:
class NewLotteryGameType(LotteryGameType):
def __init__(self, numbers):
super().__init__(name, size, minmax[0], minmax[1])
self.numbers = numbers
def __eq__(self, rhs):
return set(self.numbers) == set(rhs.numbers)
return NewLotteryGameType
So:
>>> SuperLotto = make_lottery_set_type('SuperLotto', 6, (1,50))
>>> super1 = SuperLotto([1,2,3,4,5,6])
>>> super2 = SuperLotto([6,5,4,3,2,1])
>>> super3 = SuperLotto([7,8,9,10,11,12])
>>> super1 == super2
True
>>> super1 == super3
False
(Obviously you can define __eq__ however you want, if set-equality isn't the right rule for your use.)
If you try to inspect the values you're generating, they don't look quite as pretty as you might like. For example, you'd probably rather see SuperLotto rather than NewLotteryGameType in places like this:
>>> super1
<__main__.NewLotteryGameType at 0x10259e490>
>>> SuperLotto.__name__
'NewLotteryGameType'
For that, just add NewLotteryGameType.__name__ = name. You might also want to copy over the docstring from the parent class, or various other things.
More generally, look at functools.update_wrapper (which is designed for wrapping up functions, not classes, but many of the details are the same) for inspiration, and the inspect module docs from your Python version for all of the attributes that classes can have.
In a comment, you ask:
The only problem is that I want NewLotteryGameType to inherit the parameters such as name, set_size, min_set_number, max_set_number from LotteryGameType. So lets say I wanted to type in NewLotteryGameType.set_size in to the Python Shell. I want it to return back to me 6.
That's contradictory. If you want to inherit the instance attributes of LotteryGameType… well, you already do. For example:
>>> super1.set_size
6
If you want them to be accessible off the class, then they can't be instance attributes, they have to be class attributes. And just changing set_size to a class attribute of LotteryGameType and inheriting it won't work, because the whole point of a class attribute is that the same value shared by all instances of the class or any of its subclasses, and the subclasses all need different values.
But you could do something like this:
class LotteryGameType:
def __init__(self, min_set_number, max_set_number):
self.min_set_number = min_set_number
self.max_set_number = max_set_number
def make_lottery_set_type(lottery_name:str, size:int, minmax:tuple):
if minmax[0] > minmax[1]:
raise LotterySetError('Illegal range for tuple')
else:
class NewLotteryGameType(LotteryGameType):
name = lottery_name
set_size = size
def __init__(self, numbers):
super().__init__(minmax[0], minmax[1])
self.numbers = numbers
def __eq__(self, rhs):
return set(self.numbers) == set(rhs.numbers)
return NewLotteryGameType
(Notice that I had to rename the first make_ parameter to lottery_name so it was different from the class attribute name, because of the way scopes work.) Now, name and set_size are not instance attributes, nor are they class attributes of LotteryGameType—but they're class attributes of each NewLotteryGameType. So:
>>> SuperLotto = make_lottery_set_type('SuperLotto', 6, (1,50))
>>> SuperDuperLotto = make_lottery_set_type('SuperDuperLotto', 8, (1,100))
>>> SuperLotto.set_size
6
>>> SuperDuperLotto.set_size
8
What if you create instances of those types? Well, Python looks for attributes in the instance, then in the most-derived class, and then the base classes. So as long as you don't create instance attributes with the same name (notice that I removed the extra params, and the code that set instance attributes, from the LotteryGameType.__init__ method), it does just what you'd want:
>>> super1 = SuperLotto([1,2,3,4,5,6])
>>> super1.set_size
6
>>> duper1 = SuperDuperLotto([1,2,3,4,5,6,7,8])
>>> duper1.set_size
8
Of course this means that LotteryGameType is no longer a usable type on its own; only its subclasses are usable. But that's probably what you wanted anyway, right? You could even consider making it explicitly an abstract base class to make sure nobody accidentally tries to use a direct LotteryGameType instance.
If you're feeling brave, you might want to read up on metaclasses and see how you could adapt this whole design into use a LotteryGameMetaclass, so each new class is an instance of that metaclass instead of a subclass of the (abstract) base class. The source for the new enum module in 3.4, or the near-equivalent external flufl.enum package, might make good sample code. Then you can play with both and see how similar and how different they are.

Python create objects in for loop

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.

Categories