Python, why do we have to inherit from 'list' class - python

I'm learning object oriented python and came across this issue where python is forcing me to inherit from built-in 'list' class to use append method.
class ContactSearch(list): #Why inherit from 'list'?
def search(self, name):
matching_contact = []
for contact in self:
if name in contact.name:
matching_contact.append(contact)
return matching_contact
Why can't I simply declare an empty list and append to it? For example it works find in the following code without inheriting from 'list class':
class Contact:
all_contacts = []
def __init__(self, name, email):
self.name = name
self.email = email
Contact.all_contacts.append(self)

In ContactSearch, matching_contact is temporary variable for result of searching. A instance of ContactSearch is list. So search method can use self in iterator. matching_contact is over when search method is done.
In Contact, all_contacts is class variable and all instance of Contact share this. Instance of Contact is NOT list. But you can access all contact using like Contact.all_contacts, and yes, it is list.
The difference is only where the data is stored. The one stores data in itself, and the another stores data in its variable.

Related

What's the point of declaring variables outside of constructor?

Take the following example:
class User:
name = ""
def __init__(self, name):
self.name = name
What is the point of declaring name = "" if you have to specify name anyway when you construct it?
You'd have to ask the person that did it, since that is a very broad question. Maybe git blame will help.
In general there is no point. They're different things: class attribute and instance attribute. One would be data associated with the type of object, the other with individual instances of the object.

How to get class instanced using variable inside of the class

I am trying to access a class instance. I can't assign the class to a variable when I load it and then use it because I need to access the class based on what the user enters.
i.e: user goes to link website.com/classes/y, I need to access the instance with the name y.
I already handle the link and can get "y" or whatever the user entered by itself.
I have the class code as follows:
class LoadModel:
existing_models = []
def __init__(self, model_path):
self.name = model_path.parent.name
self.__class__.existing_models.append(self.name)
For now, I can verify if the class exists using the existing_models list, but how will I be able to access it using the self.name?
I want to access it using LoadModel.name.
It sounds like you want to keep a dictionary of model names to instances. You could do that with something like:
class LoadModel:
modelsByName = {}
def __init__(self, model_path):
self.name = model_path.parent.name
self.modelsByName[self.name] = self
Furthermore if you wanted to access an instance named name as LoadModel.name you could could add
setattr(self.__class__, self.name, self)
to __init__. Or if you were looking up by string (which it sounds like you might be) then you would just do LoadModel.modelsbyName[name].
Note also that you don't need to use self.__class__ when accessing members of the class that you have not assigned within the instance, and since you're only accessing the dictionary object defined in the class, you can use the reference inherited by the instance (self.modelsByName) instead of accessing the class explicitly (self.__class__.modelsByName).

Edit parent object's attributes in a nested object

I am trying to solve a problem where I have a class which is used to store objects of other classes. I want to be able to create a list in that parent object of certain types of nested objects. (sorry if I'm phrasing this badly)
For my example I am taking a real world problem of a House. In the house we may have many different 'things'. We could have people, pets, furniture, computers etc.
I want to be able to store this in a nested format so I create a House with any of those sub-objects within.
E.g
old_rectory = House(
Person('Barry'),
Person('Bob'),
Pet('Bill'),
Pet('Brenda')
)
I have created 3 classes: the first is for the House itself, then I have a class for people and a class for pets.
For the house class I use *argv so that I can add as many other objects as necessary.
Now in the example above I would like to be able to access old_rectory.people and see a list of any instances in old_rectory that are of the type Person.
class House:
def __init__(self,*argv):
self.people = []
self.pets = []
for arg in argv:
if isinstance(arg, Person):
self.people.append(arg)
elif isinstance(arg, Pet):
self.pets.append(arg)
class Person:
def __init__(self,name):
self.name = name
class Pet:
def __init__(self,name):
self.name = name
You can see that I have achieved this by hard coding the lists and some if conditions into the House class. But this means that whenever I create a new class I also need to add in a new list and the logic to the House class.
I would like to be able to access the house object's attributes from the individual classes (Person, Pet, etc.) but I am unsure of how to proceed or if it is even possible.
class Doors:
NOW CREATE THE NEW LIST IN HOUSE CLASS
def __init__(self,name):
self.name = name
NOW APPEND THIS OBJECT TO THE NEW LIST IN THE HOUSE CLASS
I can see two clear patterns on how to try to overcome this:
Create methods for getting the instances of a specific class
Simply put every instance inside a big list and add methods to get each "sublist":
class House:
def __init__(self,*argv):
self.things = [*argv]
#property
def pets(self):
return [thing for thing in self.things if isinstance(thing, Pet)]
#property
def people(self):
return [thing for thing in self.things if isinstance(thing, Person)]
This doesn't really solve your initial problem, but at least it's easier and cleaner to implement for new classes - if a list attribute does not exist, it's because you haven't implemented the method for it.
Use hasattr, setattr and getattr
Use these functions on the __init__ method to programatically check if each list exists, create them if needed and append each instance to the corresponding list:
class House:
def __init__(self,*argv):
for arg in argv:
name = arg.__class__.__name__.lower()
if not hasattr(self, name):
setattr(self, name, [])
getattr(self, name).append(arg)
I personally think this is worse, since your attributes will be named exactly like the class name (i.e. person, not people), and you can't clearly see which lists are initialized as attributes or not since it's done on the fly, but it should work for your described use case.
Whichever way you decide to go with, note that I personally feel like your design isn't very effective for dealing with this problem. I'd rather create empty lists for people, pets etc on the House's __init__ method and add specific methods like add_person, add_pet, add_group etc for appending objects to the House's list attributes. It may not seem like much, but this design:
a) clearly defines supported classes that can interact with the House class; and
b) lets you see more clearly exactly who is getting put into the House, since you need to explicitly call the method in order to do so.
I have restructured your code. Check it out :)
class House:
def __init__(self,*argv):
self.house_classes = {"people": Person, "pets": Pet}
self.house_objects = {}
for object in argv:
self.add_house_object(object)
def add_house_class(self, class_id, class):
self.house_classes["class_id"] = class
def add_house_object(self, object):
for class_id in self.house_classes:
if isinstance(object, self.house_classes[class_id]):
if class_id in self.house_objects:
self.house_objects["class_name"].append(object)
return
self.house_objects["class_id"] = [object]
class Person:
def __init__(self,name):
self.name = name
class Pet:
def __init__(self,name):
self.name = name
To add new classes (e.g Doors) to a house object (as i think you want)
my_house = House(house_objects...) #initialise a house object
class Doors: #your new class to add to house object
def __init__(self,name):
self.name = name
my_house.add_house_class(self, "doors", Doors) #added to house object
new_door = Door("my door") #door object
my_house.add_house_object(new_door)
I hope that helps :)
You can check if House has a doors list with getattr(House, 'doors', None) and create the list if it is not existing. This solution assumes that you intend to create the list as a class variable (I am assuming this, since you do NOT pass any House-instance reference do Doors when instantiating a Doors instance).
class Doors:
def __init__(self,name):
if getattr(House, 'doors', None) is None:
House.doors = []
self.name = name
House.doors.append(self)
BUT I strongly advise you to NOT USE THIS PATTERN. This looks like a good case for class inheritance, for example creating the class Doors with class Doors(House):.
Furthermore I've got the feeling that you should take a look at the definitions and meanings of class variables and instance variables.
Imho the best way to deal with this task would be to make Doors a class which inherits from House and to require an existing instance of house to be able to create a Doors instance (for example check with if isinstance(new_house, House):). Then the Doors __init__ method could create and/or append a list doors as instance variable to House.
This way you can create many different houses. Whereas when using a class variable for the doors list, each house would have all doors created in all houses.
Thus I recommend using this pattern:
class Doors(House):
def __init__(self, name, house):
if not isinstance(house, House):
raise ValueError('`house` is not an existing instance of the `House` class')
if getattr(house, 'doors', None) is None:
house.doors = []
else:
print('exi')
self.name = name
house.doors.append(self)
As jfaccioni pointed out: Inheritance is not mandatory here, but this kind of construct looks like you are going to need it in the long term for method-access etc.

What does append(self) mean in Python classes?

I am new to OOP in Python and working on inheritance concept. I came across the following code:
class ContactList(list):
def search(self, name):
'''Return all contacts that contain the search value in their name.'''
matching_contacts = []
for contact in self:
if name in contact.name:
matching_contacts.append(contact)
return matching_contacts
class Contact:
all_contacts = ContactList()
def __init__(self, name, email):
self.name = name
self.email = email
self.all_contacts.append(self)
I'm wondering why do we use self.all_contacts.append(self) and how does for contact in self work ?. If I understood correctly, self appoints to the instance of a class (object), and appending to a list is not trivial to me.
all_contacts is a class variable -- it is unique to the class, not to each instance. It can be accessed with Contact.all_contacts. Whenever you create a new contact, it is appended to this list of all contacts.
ContactList inherits from list, so for contact in self works the same way as for i in [1,2,3] -- it will loop through all the items that it contains. The only thing that it does differently from a list is implement a new method, search.
Well, basically you create a list of Contact and appending self add the current contact in the all_contacts list.
Now for your questions,
I'm wondering why do we use self.all_contacts.append(self)
We would use that because all_contacts is a class variable which means that the list will be shared among all Contact instances.
how does for contact in self work?
Well, as you said, since self represents the current instance, calling for contact in self is allowing you to iterate on the current Contacts list.
In other words, your code sample let you create Contact instance which is appended in a class variable (shared) automatically. Now, by providing a ContactList class that inherits from list, they allow you to use the implemented search method which will return you another list of Contact based on your search filter.
all_contacts is a class variable of Contact, initialized as an instance of ContactList, a subclass to list, so when a new Contact instance is instantiated via the __init__ method, self is assigned with the new instance being instantiated and self.all_contacts.append(self) would add the new Contact instance to the all_contacts list. This way, Contact.all_contacts will maintain a list of all Contact instances that have been instantiated.

method to print name of an instance of a class

I am new to classes and writing one to perform a tracking and timing task. Have looked at this but still having trouble getting one aspect of the functionality to work.
Here's the part of what I've got to demonstrate the problem:
class seperate_trackers():
def __init__(self):
print ("class initiated")
def print_instance_name(self):
print (self.__class__.__name__)
Create an instance of it:
track_task1 = separate_trackers()
>> class initiated
Run the method in there:
track_task1.print_instance_name()
>> separate_trackers
That's not what I want!
How can that method be fixed so it returns track_task1 when it is run?
This is not a good idea. If you want your instance to have a name, that should be an attribute of the instance itself (the name of the variabe is just a pointer and it should not represent the object's state).
Try this instead:
# We don't usually use snake case for class names in python (and its 'separate')
class SeparateTrackers():
def __init__(self, name):
self.name = name
instance1 = SeparateTrackers("instance_name")
print(instance1.name) # instance_name
Objects don't know what variables refer to them. There can be any number of references to an object, and none of them is "the real one," they are all equally valid as names for the object. Furthermore, there may be no references that are simple names:
things = [1, "hello", separate_trackers(), 3.14]
There's no useful way to find out what variables refer to an object.
class SeparateTrackers:
def __init__(self, instance_name):
self.instance_name = instance_name
def __str__(self):
return self.instance_name
So you can use something like
a = SeparateTracker("first instance")
print(a) # print instance's name

Categories

Resources