Reputation: 377
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
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