Consider the following example:
class Company():
def hireEmployee():
def fireEmployee():
def promoteEmployee():
etc...
class EngineeringFirm(Company):
pass
class PaintingFirm(Company):
pass
Suppose the Company class has a lot more methods. What if I want to rename these methods from the superclass so I can get the following:
class EngineeringFirm(Company):
def hireEngineer():
...
class PaintingFirm(Company):
def hirePainter():
...
...and so on. While using 'Employee' in this scenario really wouldn't hurt a bit, this is really just to illustrate the idea. How would I go about it?
My idea was to use a classFactory function that would take the type of employee as argument and generate a Company class, while a metaclass would handle the renaming by iterating through the attribute dictionary and replacing 'Employee' with said type.
class EngineeringFirm(companyFactory('Engineer'))
...
The only problem is this: What if the methods inside of Company make calls to one another by the default 'Employee' names? This is where I'm stumped. I had the idea that the metaclass involved in renaming the methods could also get the source of each function (via the inspect module) and search if a known method attribute is found within and, if so, replace that part and create a new function via exec and assigning it back to the right attribute key.
...But that really seems kinda of hacky. I am open to alternatives and although I realize there may be design-related issues with the question (I am open to suggestions on that front as well) I would be interested in finding out if this problem has a more elegant solution.
Thanks!
Edit: another solution
For the sake of argument, I'll assume for a moment that the code above is really what I'm working with; I figured I could address some of the concerns in the comments with another solution I had in mind, one I'd already considered and put away for reasons I'll explain.
If the Firm classes inherited from Company and I wished to maintain a identical interface (as one usually would in a case like this to allow dynamic calls to hire() or promote(), etc) I could implement a __getattribute__ that accepts HirePainter() (by accessing the original Employee method) while still allowing any other interface to use the HireEmployee() if necessary.
I wonder, supposing it's alright to extend my question, if this is something that would be considered bad practice if, say, I planned to do this because I thought that the code inside PaintingFirm would benefit in readability? Again, I realize this example is horrid in that readability here really does not seem to benefit in any way whatsoever, but suppose it did?
(The only reason I didn't suggest this idea in the first place is that my __getattribute__ already handles quite a bit, and adding extra noise to it didn't feel that appealing. Still, I could work it in, but this is a question I had to ask in case there were more magical (but not hacky) solutions out there..)
For posterity's sake, I'm posting a solution of my own that I believe is a decent alternative. I don't suggest this as the answer because the truth is I did not mention in my question that I preferred not adding extra names, or to retain the ability to call these attributes as self.hireEngineer rather than ClassDict['HireEngineer']. Given that, I can't really say any of these answers don't answer the question.
Solution:
In hindsight, the problem was a lot simpler than I made it out to be. I guess I got hooked on the metaclassery just for the sake of it. If it's not already obvious, I'm really only just learning about metaclasses and for a moment it seemed like a good opportunity to try them out. Alas.
I believe the following solution respects the spirit of Liskov's principle (thank you, Ignacio) while giving the derived class the ability to reference the derived methods in its own way. The class namespace stays the same and other objects can call upon these methods with their real names if necessary.
# superclass...
def __getattribute__(self, attr):
# Early exit (AttributeError) if attribute not found.
obj = object.__getattribute__(self, attr)
# All the extra code...
def __getattr__(self, attr):
# Ex. self.type == 'Engineer'
# Replacing titled-cased and lower-cased
# versions just to be safe (ex. self.employeeNames)
attr = (attr
.replace(self.type, 'Employee')
.replace(self.type.lower(), 'employee')
)
if attr in self.attributes:
return self.__getattribute__(attr)
else:
raise AttributeError
I'll try to do a better job next time around when outlining the requirements. Thanks, guys.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With