Python - cannot access instances of a class - python

I am stuck with a problem that is probably quite simple, yet I cannot figure it out. I am trying to develop a desktop application for creating timetables. I am using Tkinter and Python 3.6. I have a Teacher class, so the user can create new instances with various attributes.
class Teacher:
"""The properties of the teachers"""
allTeachers = []
def __init__(self, name, lessons, timeframe):
self.name = name
Teacher.allTeachers.append(self.name)
self.lessons = lessons # number of lessons
self.timeframe = timeframe
Once a new instance is created I check that it exists:
for obj in gc.get_objects():
if isinstance(obj, Teacher):
print(obj.name)
However, when the user adds another teacher, the above code says that the Teacher class still has only one instance (the latest one). Moreover, when I run the same code from another module (in the same directory), Python tells me that the Teacher class has no instances. In spite of this the class variable (allTeachers) keeps track of all the teachers that have been added.
Am I missing something basic about accessing objects?

Python frees memory, if you do not hold any reference to your instances. You are only storing the Teacher's names - not the instances of it. So if you happen to just create them like this:
Teacher("Hugo", None, "8am to 2pm")
Teacher("Claas", "some", "9am to 10am")
there is no reference to the actual instance and they get garbage collected.
Additionally information can be read here: Python garbage collector documentation
If you want to look up things, lists are bad if you have more then around 4 items, they got a Lookup of O(n). Use a set or dict for O(1) instead. If you want to lookup Teacher by name, dict would be convenient:
class Teacher:
"""The properties of the teachers"""
allTeachers = {} # dict as lookup is faster then list for 4+ teachers
def __init__(self, name, lessons, timeframe):
self.name = name
Teacher.allTeachers[self.name] = self # store the instance under its name
self.lessons = lessons # number of lessons
self.timeframe = timeframe
#classmethod
def hasTeacher(cls,name):
return name in Teacher.allTeachers
Teacher("Hugo", None, "8am to 2pm")
Teacher("Claas", "some", "9am to 10am")
import gc
print(Teacher.hasTeacher("Judith"))
print(Teacher.hasTeacher("Claas"))
for obj in gc.get_objects():
if isinstance(obj, Teacher):
print(obj.name)
Output:
False # no teacher called Judith
True # a teacher called Claas
# all the Teacher instances
Hugo
Claas
If you store Teacher instances this way, you probably should provide a way to remove them by name from the class variable instance as well - and maybe return the Teacher-instance by name from it

Related

Infinite call to python class variable

So, I'm fairly new to Python, and I am trying to learn how the OOP paradigm works there.
One thing that confuses me at this stage are the class variables. To be more specific, consider the following example taken from the Python 3 Object-Oriented Programming book by Dusty Phillips (Chapter 3 - When objects are alike):
class Contact:
all_contacts = []
def __init__(self, name, email):
self.name = name
self.email = email
Contact.all_contacts.append(self)
So, here we see a simple example of a class that has the class variable all_contacts which is an empty list. Besides that there are two instance variables, name and email.
Now, if we were to create two different instances of the class Contact, e.g.
c1 = Contact('John', 'john#gmail.com')
c2 = Contact('Mark', 'mark#yahoo.com')
there are two ways how we can access properties name and email, i.e.
c1.name
c1.all_contacts[0].name
But this is where I have a problem - all_contacts is a class variable, meaning that it is common to all instances of the class Contact. Not only that it is common to all instances, but due to the way how this list is being populated, there is nothing that prevents me from doing this:
c1.all_contacts[1].name
which gives me the name property of the instance c2.
In fact, it seems that I can go deep as much as I want:
c1.all.contacts[0].all_contacts[1].all_contacts[0]...
In other words, I am worried if in Python we have a class with a class variable as a list and then in the initializer method we store self to that list, are we then creating a structure that can reference itself infinite many times? Is there some kind of workaround for this?
I would really appreciate if anyone can comment on this behavior.
Kind regards

How to instantiate an object in a new class?

A bit of an odd question, but I'm wondering how to import an object from one class to another. I imagine adding more class methods and attributes as I expand my program, and I still want to be able to use old data. I am thinking something as follows:
class old_obj:
def __init__(self, text):
self.name = text
def set_amount(self, num):
self.amount = num
def introduce_yourself(self):
print("I am {} and I am {} many".format(self.name, self.amount))
oldest = old_obj("myself")
oldest.set_amount(15)
also_old = old_obj("Bach")
class new_obj:
def __init__(self):
#some code
#more code
I want to be able to write something like:
renewed = new_obj(oldest)
also_new = new_obj(also_old)
Here, I want to retain the 15 from oldest.amount, but not complain that also_old.amount is None. In particular, I want to retain any attributes that oldest has, while not requiring that it have all possible attributes. Is there a way for me to copy over instances of a class to a new class?
Edit: edited for clarity
You could copy the object instance dict to the new class.
from copy import deepcopy
class old_obj:
def __init__(self, text):
self.name = text
def set_amount(self, num):
self.amount = num
def introduce_yourself(self):
print("I am {} and I am {} many".format(self.name, self.amount))
oldest = old_obj("myself")
class new_obj:
def __init__(self, my_old_obj):
for var, val in my_old_obj.__dict__.items():
setattr(self, var, deepcopy(val))
#some code
#more code
newest = new_obj(oldest)
I did a deepcopy of the value assuming you want unique values in the new object. But that can also be problematic because not everything can be copied (file objects for instance). There can be other oddities when duplicating attributes such as what you want to do with a generator. And if this is something like a GUI widget, it could get stranger still.
But for a lot of object types, this would work.
Slightly different take:
Your new class has a set of concerns that are probably similar to your old class. This should guide the way you update it and build out the behavior in question. With this in mind...
Provide a class method in your new class to allow construction of the new object from the old object. Don’t make this behavior a part of __init__. Your __init__ should have a more limited responsibility. For the class method, updating the new object’s __dict__ using the old object’s __dict__ would do the job.
Don’t use inheritance to make new versions of classes. Use inheritance to move from general to specific or abstract to concrete. Otherwise, you end up with code that is hard to understand and update. (Imagine several generations down of just sub-classing in order to add some new methods.)
If the number of methods and attributes is growing, you might want to consider whether or not you’re encapsulating data/behaviors that should be split into multiple classes. The guiding principle is that you should encapsulate the data/behaviors that are likely to change together. That is, when you change the way you’re implementing your program, things that don’t need to change should probably be encapsulated separate from things that need changing. If you find that a lot of your static data is bound up with an object class that you’re frequently updating (but wanting to just import the old data unchanged), then you’ve probably got two different sets of concerns, at least.
You can simply initialize the new object by passing it the old one.
class old_obj:
def __init__(self, text):
self.text = text
oldest = old_obj("myself")
class new_obj:
def __init__(self, old_inst):
self.text = old_inst.text
renewed = new_obj(oldest)
print(renewed.text)
First, make your new_obj class inherit from old_obj, so that new_obj has all the methods old_obj had:
class new_obj(olb_obj):
Then, in __init__ method of the new class you can check what is passed as the argument - a string or an object:
def __init__(self, arg):
if isinstance(arg, str):
self.text = arg
elif isinstance(arg, old_obj):
self.text = arg.text
else:
raise TypeError

How to call an instance of a class without knowing the name beforehand

Say I have different instances of a class;
class Person(object):
def __init__(self, id):
self.id = id
Joe = Person('123')
Sarah = Person('321')
My question now is, how would I be able use one of the instances without knowing the name before hand, for example, I could have something that requests for a name or even id. How would I associate it with the appropriate object? Say an input of an id, '123' was given, how do I get to know that it belongs to the Joe object and then use that object dynamically? I am running Python 3.6 if that info is of much use.
As you study computer programming, you will probably hear a not-really-a-joke that there are only three important numbers: 0, 1, and infinity. In other words, you can have none of something or one of something, but if you can have more than one, you have to be prepared to handle any number of them.
When you can only have zero or one of something, putting it in a variable is fine. But this solution quickly becomes unwieldy when you can have any number of something. The solution favored by programmers is to put the objects in a container, like a list or dictionary.
For example, the dictionary approach:
people = {"123": Person("123"), "321": Person("321")}
Then Joe is people["123"].
In reality you want to store the person's name in the object as well, otherwise you can't tell who it actually represents, so you'd add that to your Person class. You could also have the object add itself to the master people dict when it's instantiated:
people = {}
class Person(object):
def __init__(self, id, name):
self.id = id
self.name = name
people[id] = self
joe = Person("123", "Joe")
sarah = Person("321", "Sarah")
Now when you instantiate objects they are added to the people dict by their ID automatically, and you can access them as such. (You could also add them by name, assuming the names are unique: even to the same dictionary if IDs and names can never clash.)
N.B. "calling" has a specific meaning in programming: you call a function. You are not calling anything here.

Python: adding an attribute to a variable of a class and have it survive assignment (descriptor class)

our project collects a bunch of information on an object from a bunch of different sources and for auditing and for resolving conflicts between our data sources we need to keep a bunch of meta data about each attribute
age = 21
age_metadata = {
'source' : 'office gossip',
'relability' : 0
{
the problem is the classes start to look like this.
class Person
def __init__(self, id, ):
self.id = id
self.id_meta = None
self.age = None
self.age_meta = None
self.name = None
which looks ugly and doubles the number of attributes everything has so what I want is to nest the metadata under each attribute. so if I need it I can call bob.age.meta instead of bob.age_meta and I want to do this while leaving the attributes more or less unchanged. so I want something that always me to use it like:
def main():
bob = Person(1)
bob.age.meta = 'random metadata'
bob.age = 30
assert bob.age.meta == 'random metadata'
Now I think this should be possible using a combo of a descriptor class to intercept the assignment + some monkey patching/ metaclassing magic to preserve the metadata.. but I'm new to all these features and am getting very lost. here is my current sketch.long way from working code:
class AttributeWithMeta(object):
"""interceot attribute fucntions"""
def __get__(self, instance, owner):
return self._value
def __set__(self, instance, value):
"""take attribute and somehow replace it with monkey patched version"""
self._value = magic(value,meta)
#staticmethod
def magic(value, meta):
"""adds meta attribute to objects regardless of type"""
return value_with_meta
class Person(object):
age = AttributeWithMeta()
def __init__(self, id):
self.id = id
Am I going in the right direction? is there some other way to do this that I'm missing? Am I trying to do the impossible here?
P.S worst case age.get_meta() would be acceptable if attributes are problematic.
bob.age.meta
^ ^
| |_______ This lookup can be managed by type(bob.age) class
|
|___________ This lookup can be managed by type(bob) class, i.e. Person
The attribute access of .meta is handled entirely by the bob.age object, so your Person class gets no say in the matter here.
By defining descriptors on Person class, you can control how setting/getting/deleting the age attribute behaves on a Person instance. You own the dot between a person instance and an attribute, and can do anything you want there. But that power does not reach as far as the 2nd dot - i.e. the . between age and meta.
If you want bob.age to return literally the integer 30, then you are going to have a hard time getting bob.age.meta to do anything but raise AttributeError. That's because you would need a descriptor to be defined on the int class, now, and monkey-patching built-in types is not possible in python. Not without terrible and unreliable hacks, anyway.
If you are happy for bob.age to return some kind of container of an object and the associated metadata, then what you want is possible with descriptors on the Person class. But if you want bob.age to return only the object, and bob.age.meta to return only the metadata, then I think you're barking up the wrong tree.
A much less complicated design would be for each Person instance to have a meta dict directly, so that you could look up for example bob.meta['age'] for the metadata related to bob's age attribute.

Python Classes and Collections

This is a best practices question
Let say, I have a class object, like so:
class ClassOfObjects:
def __init__(self, name):
self.name = name
...
Lets say, I instantiate 3 of these objects
a = ClassOfObjects('one')
b = ClassOfObjects('two')
c = ClassOfObjects('three')
Now, I want to create a list of my objects. One obvious way is to create list object
ListOfObjects = [a,b,c]
I find that limiting. Specially when I trying to search find an object with a particular object. Is anyone aware of any best practices.
You can have each instance register itself with the class when it's created:
class K(object):
registry = {}
def __init__(self, name):
self.name = name
self.registry[name] = self
Then K.registry is a dictionary of all the instances you've created, with the name as the key. You don't even need to assign the instance to a variable, since it's accessible through the registry. You can also iterate over the instances easily.
Perhaps if you share more information about your use cases, someone can provide a better alternative.

Categories