Creating classes in dictionaries - values being duplicated between classes? - python

So I'm trying to store some lists into a list that belongs to a "person". I tried to do this with some classes:
class data():
# Contains list of x, y, z, time lists
x = []
y = []
z = []
time = []
class data_main():
# Contains data for each Pi
data_plot = data()
data_overflow = data()
piList = ["Lui", "Wing"]
rpi_data = {}
for pi in piList:
rpi_data[pi] = data_main()
rpi_data["Lui"].data_plot.x = 10
rpi_data["Wing"].data_plot.x = 99
print(rpi_data["Lui"].data_plot.x)
print(rpi_data["Wing"].data_plot.x)
Problem is, I won't actually know how many people there will be. Therefore I want to create a dictionary of the class "data_main" belonging to different people.
When I try to do this, the console results are:
99
99
When I'd rather it do: 10 and 99 respectively. I'm worried that in the for loop:
for pi in piList:
rpi_data[pi] = data_main()
I'm really just designating the same instance of data_main() to the dictionary entries, when really I'd prefer unique ones, so that they can each have their own values.
How do I achieve this?
EDIT: I did more digging and it turns out data_plot for both instances of data_main() is pointing to the same address. How do I avoid this (ie, every time I init a new data_main() class, that I create new data_plot() and data_overflow() classes too?)

Your data_plot and data_overflow are class attributes not instance attributes ,so they are initialized when the class gets defined , not when the instance is created, and they get shared between all the instances of the class . You should create them as instance attributes in __init__() method. Example -
class data_main:
def __init__(self):
# Contains data for each Pi
self.data_plot = data()
self.data_overflow = data()
Also in your data class , the attributes in it are also class attributes, you should make them instance attributes as well. Example -
class data:
def __init__(self):
# Contains list of x, y, z, time lists
self.x = []
self.y = []
self.z = []
self.time = []
Finally, first in data class you are defining x as a list, and then you are changing the x for rpi_data["Lui"].data_plot.x to a number , when you do -
rpi_data["Lui"].data_plot.x = 10 #or the 99 one.
If you intended to append the number to the x list , you should use -
rpi_data["Lui"].data_plot.x.append(10)

Related

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

Add attribute to an Instance of an existing Class in Python 2

In my data, I am getting two dictionaries at two different parts. I created a Class object to combine the dictionaries into a single object.
In this example, x and y attributes are the Class dictionaries that are being assigned to D_A_x and D_A_y respectively.
How can I add an attribute to a class instance that has already been created?
class Class_Example:
def __init__(self,x="NaN",y="NaN"):
self.x = x; self.y = y
def get_x(self,var): return(self.x[var])
def get_y(self,var): return(self.y[var])
D_A = {}
D_A_x = {"a":1}
D_A["instance_1"] = Class_Example(x = D_A_x)
#D_A["instance_1"].get_x("a")
D_A_y = {"b":2}
D_A["instance_1"].__init__(y = D_A_y)
print D_A["instance_1"].get_x("a") #Doesn't work because it has been replaced.
I don't have access to D_A_x and D_A_y at the same time create the Class in one go.
I checked out this, but it didn't help in my situation: Adding base class to existing object in python
Yes, its as you have already noticed, when you call __init__() for your Class_Example class , it overwrites the x and y values with the value passed in , and in the second time, since you do not send in anything for x, it uses the default "NaN" string .
You should not call __init__() again, you can simply set the y attribute. Example -
D_A["instance_1"].y = D_A_y
Why not simply do: D_A['instance_1'].y = D_A_y?

Reading binary file to a list of structs, but deepcopy overwrites first structs

I am reading a binary file into a list of class instances. I have a loop that reads data from the file into an instance. When the instance is filled, I append the instance to a list and start reading again.
This works fine except that one of the elements of the instance is a Rect (i.e. rectangle), which is a user-defined type. Even with deepcopy, the attributes are overwritten.
There are work-arounds, like not having Rect be a user-defined type. However, I can see that this is a situation that I will encounter a lot and was hoping there was a straightforward solution that allows me to read nested types in a loop.
Here is some code:
class Rect:
def __init__(self):
self.L = 0
class groundtruthfile:
def __init__(self):
self.rect = Rect
self.ht = int
self.wt = int
self.text = ''
...
data = []
g = groundtruthfile()
f = open("datafile.dtf", "rb")
length = unpack('i', f.read(4))
for i in range(1,length[0]+1): #length is a tuple
g.rect.L = unpack('i',f.read(4))[0]
...
data.append(copy.deepcopy(g))
The results of this are exactly what I want, except that all of the data(i).rect.L are the value of the last data read.
You have two problems here:
The rect attribute of a groundtruthfile instance (I'll just put this here...) is the Rect class itself, not an instance of that class - you should be doing:
self.rect = Rect() # note parentheses
to create an instance, instead (similarly e.g. self.ht = int sets that attribute to the integer class, not an instance); and
The line:
g.rect.L = unpack('i',f.read(4))[0]
explicitly modifies the attribute of the same groundtruthfile instance you've been using all along. You should move the line:
g = groundtruthfile()
inside the loop, so that you create a separate instance each time, rather than trying to create copies.
This is just a minimal fix - it would make sense to actually provide arguments to the various __init__ methods, for example, such that you can create instances in a more intuitive way.
Also, if you're not actually using i in the loop:
for _ in range(length[0]):
is neater than:
for i in range(1,length[0]+1):

Runtime variable creation Python 3.1.2

How would i go about making a function to create a certain number of uniquely named variables at runtime, based on initial user input? For instance, user enters dimensions 400 x 400, (x and y), so i would want the function to create 1600 (400 * 400) variables, each to represent every different point on a grid 400 by 400.
What you really want is an array, a list or a tuple of 400*400 points.
So create a class that stores the information you want at each point, and then create a list of size 400*400 of those class objects.
You can do it this way:
width = 400
height = 400
m = [[0]*width for i in range(height)]
And then access points in your field like so:
m[123][105] = 7
To set point (123,105) to 7.
If you want to store more than just a number at each point, create a class like I suggested:
class MyClass:
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
And then create your list of "MyClass" objects like so:
m = [[MyClass(0,0,0) for i in range(400)] for j in range(400)]
Are you sure you need to create a different variable for each point on the grid? If there are lots of points with default value of say 0, don't create an array with a bunch of 0s. Instead, create an empty dictionary D = {}. Store data as D[(x,y)] = anything. Access your data by D.get((x,y), 0). Where D.get(key, default value) This saves memory.
Btw, 400*400 is not 1600. Rather 160,000

Categories