4

I am wanting to initiate multiple instances of a sub classes that each take their attributes from a specific instance of a class, rather than the general class.

For example if I have buildings and rooms, every room needs to belong to a specific instance of a building, taking its attributes and methods.

class Building:
     def __init__(self, buildingName, location):
        self.buildingName = buildingName
        self.location = location
        # lots more intersting stuff

    def Evacuate_Building(self):
        # do some stuff
        pass

class Room(Building):
     def __init__(self, roomName, roomType):
        self.roomName = roomName
        self.roomType = roomType
        # lots more intersting stuff

    def Directions(self, Current_Location):
        '''do some stuff involving this.roomName and this.location which comes from the specific instance of the class'''
        pass

So say I have three buildings: 'North', 'South' and 'West'.

Each building has its own rooms.

Looking at just the North Building it has the rooms 'N1', 'N2', 'N3', 'N4'.

In my code I would go through and initiate the three Buildings first, then I would initiate the Rooms, some how linking them back to its corresponding Building.

This allows me to use the Directions method, which utalises the attribute location from its parent class instance, which is unique to that Building, not a general value. It also needs to be be able to use the method from the parent class, Evacuate_Building.

I could get around this by passing the location data every time I initiate a room in the more standard setup using super(buildingName, location) or Building.__ init__(self, buildingName, location) however this means I have to write the location for every single Room and if I add or change something in the Building, I need to change every single room initialisation and the init code if additional attributes have been added.

I can also copy a the attributes out by passing the instance of the Building as a parameter to the init and then going this.location = building.location but this also has the same problems as above plus I do not get the methods this way.

I want some way of passing the specific instance of the Building, so that the room inherits its specific attributes and its methods.

Any feedback, suggestions, criticism, completely different ways of doing this is welcome! Thank you in advance!

Woodsy
  • 160
  • 10
  • I do not think python have so called passing specific instance of Building. Instead, you can create a sub function for building to generate rooms, and an attribute called self.rooms. And another function change_location, when you call change_location for building function, it change the location of all rooms under his attribute. – Marcus.Aurelianus Jul 22 '18 at 01:04
  • Once built, a `Building` hardly changes location, and a `Room` hardly changes `Building` @Marcus.Aurelianus – Reblochon Masque Jul 22 '18 at 01:08
  • @Reblochon Masque, basically i am answering 'if I add or change something in the Building, I need to change every single room initialisation and the init code if additional attributes have been added.', it can be another attribute of the building. I admit I am wrong in location. – Marcus.Aurelianus Jul 22 '18 at 01:20
  • It’s worth noting that what you’re asking for isn’t what you want here, and is in fact very rarely what you want, but it’s not incoherent for some designs. It’s called _prototype inheritance_ rather than _class inheritance_. And it is possible to do it in Python, you just have to build the whole mechanism yourself, rather than using the functionality built into the language. But no major language other than JavaScript does prototype inheritance, and that JS developers have spent decades trying to build class inheritance into their language, because classes fit your object model much more often – abarnert Jul 22 '18 at 01:42
  • 1
    @Marcus.Aurelianus: Sounds like the correct solution is to have the building have attributes, and the rooms have a reference to the building containing them. `__getattr__` can then be used to make attribute access on `Room` for attributes that don't exist pull data from its building seamlessly. – ShadowRanger Jul 22 '18 at 02:17
  • @ShadowRanger, you are right. Forget about __getattr__, I am also new to python... – Marcus.Aurelianus Jul 22 '18 at 02:23

3 Answers3

5

Your design is very weird. A room is not a building, so why should Room inherit from Building?1

There is a relationship between rooms and buildings: every room is in a building, and every building contains zero or more rooms. You can represent either relationship, or both,2 by composition—that is, by adding members:

class Room:
    def __init__(self, building, roomName, roomType):
        self.building = building
        self.roomName = roomName
        self.roomType = roomType
        # lots more intersting stuff

Or:

class Building:
    def __init__(self, buildingName, location):
        self.buildingName = buildingName
        self.location = location
        self.rooms = set()
    def add(self, room):
        self.rooms.add(room)

You could even make Room.__init__ call building.add(self) if you wanted.


Notice that this gives you exactly what you wanted: each Room is related to a specific Building instance, self.building, instead of to the Building class.

So, you don't have to write the location for every single Room, you just pass the same Building instance to all of them. And if I add or change something in the Building, every Room in that building will see that change in its self.building without you having to repeat yourself or do anything special.

And not even just statically in the code. You can move a Building at runtime with self.location = new_location. Then, for every Room in that Building, its self.building.location is now new_location.


Also, you wouldn't normally want a Room to support the methods of a Building. Calling Evacuate_Building on a Room doesn't make much sense. Calling Evacuate_Room might. In which case your Building might do something like this:

def Evacuate_Building(self):
    for room in self.rooms:
        room.Evacuate_Room()

But what if you detect that a Room is on fire, and therefore you need to evacuate the whole Building that Room is in, including all of the rooms? Simple: you evacuate the Room's building attribute:

if is_on_fire(room):
    room.building.Evacutate_Building()

1. That's what inheritance means: class Room(Building): says that any Room is a Building. Or, put another way: any code that wants a Building and gets a Room will be happy with it. Sometimes there are reasons to subvert that meaning (e.g., a mixin class is just a hunk of method implementations; if you write class Skyscraper(Building, MajorConstructionMixin):, you're not really declaring that a Skyscraper is a MajorConstructionMixin because that doesn't really mean anything; you're just borrowing some convenient implementation code)—but that's still the meaning you're subverting.

2. Be aware that if you add references in both directions, you have a reference cycle. This isn't illegal, or even usually a problem; it just means that when you abandon a building and all of its rooms, the CPython garbage collector won't find them and delete them immediately, until the next run of the collector. Still, while it's not a big deal, most programs don't need relationships in both directions, so you should see if you can simplify your design by eliminating one.

abarnert
  • 354,177
  • 51
  • 601
  • 671
4

A Room is not a Building ==> Room should not be a subclass of Building.
A Building has many Room ==> use composition.

One possible approach is to have each Building have a collection of Room

class Building:
    def __init__(self, name, location):
        self.name = name
        self.location = location
        self.rooms = []            # or a set as in @Abamert's reply
                                   # or dict if you want to address it by floor and number, maybe?
    def add_room(self, room):
        self.rooms.append(room)

class Room:
    def __init__(self, name, model):
        self.name = roomName
        self.model = model

You could also, maybe, have rooms hold a reference to the building they are located in; this reference could be an attribute assigned upon adding a room to a building:

class Building:
    ...
    def add_room(self, room):
        room.bldg = self
        self.rooms.append(room)
Reblochon Masque
  • 35,405
  • 10
  • 55
  • 80
0

Although I realize this is an old question, I think the main issue of inheritance from instance over class merits attention.

I'm not sure if the previous answers fully understood your example of the importance of inheritance from an instance, where features cannot be detailed in advance, which by definition is required with classes.

In another issue I found an answer describing the module Acquisition which did exactly this, inheriting from an instance. It might be worth a look: Inheriting from instance in Python

moojen
  • 1,146
  • 11
  • 18