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.
Related
Suppose there are several presses, each one published many books
I am going to handle many records like 'press name, book name'
I want create the class 'Press' by a unique name(string), but only different string can produce different instances of class 'Press'.
so, I need a class work like 'factory', create a new instance when a record contains a new press, but it also work as singleton if there are already exists which has the same names.
You could create a dict with the unique names as keys and Press objects as values. This doesn't need to be a global dict. You can wrap it in some class like that:
class Press:
def __init__(self, name):
self.name = name
self.books = []
class PressManager:
presses = {}
#classmethod
def get_press(cls, name):
if name not in cls.presses:
cls.presses[name] = Press(name)
return cls.presses[name]
example_press = PressManager.get_press("Test")
I implemented get_press() as a class method, because I think this is what you had in mind.
I am new to Python and I have prior experience in VBA.
I wish to create Classes in Python such that I could achieve the following syntax:
Company('MS').Department('Finance').Employee('John').FullName = 'John MacDonalds'
Company('MS').Department('Finance').Employee('John').Wages = '5000'
I am beginning to realize that declaring the Class structure in Python is very different from VBA.
Am I on the right approach? If not, will there be any other recommended approach for this Parent/Child structure?
I really don't think that's a good use of inheritance. Good inheritance is when the child class can easily be substituted for the parent class with no difficulties. Is an employee substitutable for a department? That doesn't seem right. Wouldn't a department be able to do tons of things an employee couldn't? For every function you have for a department that an employee can't do, you would have to have a "throw new not implemented" in the child class.
This is known as the Liskov Substitution Principle. Basically subclasses must be substitutable for their base classes. My favorite example is with birds and penguins. You could make a bird class and have lots of different kinds of birds inherit from this class. But what happens when you create a penguin class inheriting from bird? Penguins can't fly but birds can. So in the penguin class, you would implement this function and would have to say "throw new not implemented." Its not good because if you put a penguin in where a function is expecting a bird, it will freak out and crash when you give it a penguin.
I hope that helps. I'm not too familiar with Python specifically but I do understand inheritance.
You haven't really said what the syntax you want is supposed to accomplish which would be useful to know...
Nesting classes as shown below would allow you to achieve the syntax you want, but the result is not only very "unpythonic" — it's also completely useless and I can't think a reasonable way to implement to change that.
Without more information about what you're doing, at the moment I can think of an alternative to suggest. I suggest you just create a bunch of regular non-nested classes and explicitly instantiate them when needed.
Note that I've changed the names you were to follow the PEP 8 naming conventions.
class Company:
def __init__(self, name):
self.name = name
class Department:
def __init__(self, dept_name):
self.dept_name = dept_name
class Employee:
def __init__(self, given_name):
self.given_name = given_name
Company('MS').Department('Finance').Employee('John').full_name = 'John MacDonalds'
Here's a way to implement your tree structure via Python "autovivification" using the Vividict dictionary defined in this answer.
I've adapted it to support different Vividict subclasses at each level in the hierarchy: i.e. Company, Department, and Employee. Each subclass defines a class atribute named subtype the the Vividict base class' __missing__() will use when creating missing key values. I also added __repr__() method to each one to make instance print out all the items they contain.
This is just a proof-of-concept and I really don't know if you can use it or not, but it should give you a good idea of one way of doing what you want with some fairly readable syntax.
class Vividict(dict): # Base class.
subtype = dict
def __missing__(self, key):
value = self[key] = self.subtype(key)
return value
class Employee: # Leaf - Regular class
def __init__(self, given_name):
self.given_name = given_name
def __repr__(self):
return f'{self.__class__.__name__}({self.given_name!r})'
class Department(Vividict): # Branch.
subtype = Employee
def __init__(self, dept_name):
self.dept_name = dept_name
def __repr__(self):
return (f'{self.__class__.__name__}({self.dept_name!r})' + '\n '
+ repr(list(self.values())))
class Company(Vividict): # Root of tree.
subtype = Department
def __init__(self, co_name):
self.co_name = co_name
def __repr__(self):
return (f'{self.__class__.__name__}({self.co_name!r})' + '\n '
+ repr(list(self.values())))
company = Company('MS')
company['Finance']['George'].full_name = 'George Brown'
company['Finance']['Mary'].full_name = 'Mary Jones'
print(company)
Output:
Company('MS')
[Department('Finance')
[Employee('George'), Employee('Mary')]]
I have an issue with class inheritence and haven't been able to find an appropriate solution elsewhere.
I have 2 classes a Parent class and a Child class.
A Child is a type of Human so I want to use class inheritance, which normally would be straightforward we just have.
class Human:
def __init__(self,type,name): # Do this when we create a Node instance
self.type = type
self.name = name
class Child(Human):
def __init__(self,name,siblings): # Do this when we create a Node instance
super().__init__(type=Child,name=name)
self.siblings = siblings
but I want to be a pain and I want to create instances by using Human.Child(parameters) rather than Child(parameters) and I don't want to have a Child class in the main scope.
I have managed to make that work by using a classmethod but it's pretty messy. Is there a more elegant solution?
class Human:
def __init__(self,type,name): # Do this when we create a Node instance
self.type = type
self.name = name
def execute(self):
print(f"executing the Human {self.name}")
#classmethod
def Child(cls,*args,**kwargs):
class Child(cls):
def __init__(self,name,siblings):
super().__init__(type=Child,name=name)
self.siblings = siblings
def execute(self):
print(f"executing the Child {self.name}")
return(Child(*args,**kwargs))
ideally it would look something like below, but of course, we cannot pass Human to the Child class as it hasn't yet been defined.
class Human:
def __init__(self,type,name): # Do this when we create a Node instance
self.type = type
self.name = name
def execute(self):
print(f"executing the Human {self.name}")
class Child(Human):
def __init__(self,name,siblings):
super().__init__(type=Child,name=name)
self.siblings = siblings
def execute(self):
print(f"executing the Child {self.name}")
When a class definition is encountered, the class object isn't created until the entire body is read, and a full dictionary accumulated. This is the opposite of how a module object is created, where an empty module is available immediately to help avoid circular imports, among other things. That being said, nothing prevents you from modifying the attributes of a class after it is fully created.
class Human:
def __init__(self,type,name): # Do this when we create a Node instance
self.type = type
self.name = name
class Child(Human):
def __init__(self,name,siblings): # Do this when we create a Node instance
super().__init__(type=Child,name=name)
self.siblings = siblings
Human.Child = Child
del Child
A few things to keep in mind:
Passing in type is redundant. You can always access that information through the type function.
Humans tend to have siblings even after they stop being children.
While I'm the one suggesting this approach, I do not endorse it. Rather than complicating things without any need, try to keep your classes in the top level namespace.
Q1. If I have a very general class, with an attribute whose name could be better represented in more specific inherited classes, how can I access the same methods from the parent class if the attribute has changed its name? For example (not my real scenario, but it shows what I mean).
class Entity(object):
def __init__(self):
self.members= {}
... # Methods that use self.members
class School(Entity):
def __init__(self):
super(Entity,self).__init__(self)
class Company(Entity):
def __init__(self):
super(Entity,self).__init__(self)
for class School and for class Company, I would like to be able to use attributes that are more specific, such as self.students and self.employees, but that still work with the methods that were defined for self.members in the class Entity.
Q2. Would this be bad practice? What would be the best way to approach this? In my real case, the word I used for self.members is too general.
Renaming an attribute in a subclass is bad practice in general.
The reason is that inheritance is about substitutability. What it means for a School to be an Entity is that you can use a School in any code that was written to expect an Entity and it will work properly.
For example, typical code using an Entity might do something like this:
for member in entity.members:
If you have something that claims to be an Entity (and even passes isinstance(myschool, Entity)), but it either doesn't have members, or has an empty members, because its actual members are stored in some other attribute, then that code is broken.
More generally, if you change the interface (the set of public methods and attributes) between a base class and dericed class, the derived class isn't a subtype, which means it usually shouldn't be using inheritance in the first case.1
If you make students into an alias for members, so the same attribute can be accessed under either name, then you do have a subtype: a School has its students as members, and therefore it can be sensibly used with code that expects an Entity:
myschool.students.append(Person(cap'))
# ...
for member in myschool.members:
# now cap is going to show up here
And this works just as well with methods defined in Entity:
def slap_everyone(self):
for member in self.members:
# this will include cap
member.slap()
myschool.slap_everyone()
And you can do this by using #property.
class Student(Entity):
# ...
#property
def students(self):
return members
#students.setter
def students(self, val):
self.members = val
#students.deleter
def students(self):
del self.members
So, this isn't flat-out invalid or anything.
But it is potentially misleading.
Will it be obvious to readers of your code that adding cap to myschool.students is going to add him to myschool.members? If so, it's probably OK. If not, or if you're not sure, then you probably shouldn't do this.
Another thing to consider is that a School might have multiple kinds of members: students, teachers, administrators, dropouts who hang around their old campus because they don't know where else to find drug dealers, … If that's part of your design, then what you really want is for members to be a property, and probably a read-only property at that,2 and each subclass can define what counts as "members" in a way that makes sense for that subclass.
class Entity(object):
#property
def members(self):
return []
def rollcall(self):
return ', '.join(self.members)
class School(Entity):
def __init__(self):
super(School, self).__init__()
self.students, self.teachers = [], []
#property
def members(self):
return self.students + self.teachers
school = School()
school.teachers.append('cap')
school.students.extend(['marvel', 'america, planet'])
print(school.rollcall())
This will print out:
cap, marvel, america, planet
That school is working as a School, and as an Entity, and everything is good.
1. I say usually because (regardless of what OO dogma says) there are other reasons for subclassing besides subtyping. But it's still the main reason. And in this case, there doesn't appear to be any other reason for subclassing—you're not trying to share storage details, or provide overriding hooks, or anything like that.
2. In fact, you might even want to drag in the abc module and make it an abstract property… but I won't show that here.
I've been trying to comprehend python's implementation of OOP.
Essentially I need something which is a superclass that defines some global attributes that al l other classes use as input for their methods. Eg:
This is how i thought it should be done:
class One():
def __init__(self, name):
self.name = name
class Two(One):
def __init__(self, name): # name from class one...
One.__init__(self, name)
def method_using_name_from_one(self, name_from_one):
return name_from_one
I guess that I could do this by just declaring all the methods in class Two as in methods of class one, but I'd much prefer to have them separated. So to recap: I want the parameters for the method in class two to use the attributes declared in class One. So essentially I want to pass in an instantiated object as the parameter arguments for class Two methods.
When you say
class Two(One):
One isn't a parameter of class Two. That means class Two inherits from class One. In other words, unless you override a method, it gets everything class One has. edit: When I say this, I mean parameters and functions, I don't mean an instance of the class. Since you have:
def __init__(self, name): # name from class one...
One.__init__(self, name)
self.name is in class Two. In other words, you could just say...
def method_using_name_from_one(self):
return self.name
One thing I would suggest is changing your class One declaration to:
class One(object):
This means it inherits from object, it doesn't mean it's getting passed an object :)
Is this what you meant? Maybe I didn't understand correctly.
If you want the name parameter from One, you could say
def method_using_name_from_one(self, oneInstance):
return oneInstance.name