cap
cap

Reputation: 377

Attribute name change for inherited classes. Possible/Bad practice?

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.

Upvotes: 0

Views: 1766

Answers (1)

abarnert
abarnert

Reputation: 365707

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.

Upvotes: 1

Related Questions