I have some code like this:
class Person(object):
def drive(self, f, t):
raise NotImplementedError
class John(Person):
def drive(self, f, t):
print "John drove from %s to %s" % (f,t)
class Kyle(Person):
def drive(self, f, t):
print "Kyle drove from %s to %s" % (f,t)
class RandomPerson(Person):
# instansiate either John or Kyle, and inherit it.
pass
class Vehicle(object):
pass
class Driver(Person, Vehicle):
def __init__(self):
# instantiate and inherit a RandomPerson somehow
pass
d1 = Driver()
d1.drive('New York', 'Boston')
>>> "John drove from New York to Boston"
d2 = Driver()
d2.drive('New Jersey', 'Boston')
>>> "Kyle drove from New Jersey to Boston"
How could i implement RandomPerson, with the following requirements:
person = RandomPerson()
must return a RandomPerson
object.RandomPerson
should subclass either John
or Kyle
randomly.In my original answer (which I deleted because it was just plain wrong) I said I would consider doing it like this:
class RandomPerson(Person):
def __init__(self):
rand_person = random.choice((John, Kyle))()
self.__dict__ = rand_person.__dict__
This way is an adaptation of the Python Borg idiom; the idea was that everything that matters about an object is contained in its __dict__
.
However, this only works when overwriting objects of the same class (which is what you are doing in the Borg idiom); the object __dict__
only contains state information pertaining to object instance, not the object class.
It is possible to switch out the class of an object like so:
class RandomPerson(Person):
def __init__(self):
rand_person = random.choice((John, Kyle))
self.__class__ = rand_person
However, doing it this way would mean that the call to RandomPerson
would then not return an instance of RandomPerson
per your requirement, but of Kyle
or of John
. So this is a no go.
Here is a way to get a RandomPerson
object that acts like Kyle
or John
, but isn't:
class RandomPerson(Person):
def __new__(cls):
new = super().__new__(cls)
new.__dict__.update(random.choice((Kyle,John)).__dict__)
return new
This one - very similar to the Borg idiom, except doing it with classes instead of instance objects and we're only copying the current version of the chosen class dict - is really pretty evil: we have lobotomized the RandomPerson
class and (randomly) stuck the brains of a Kyle
or John
class in place. And there is no indication, unfortunately, that this happened:
>>> rperson = RandomPerson()
>>> assert isinstance(rperson,Kyle) or isinstance(rperson,John)
AssertionError
So we still haven't really subclassed Kyle
or John
. Also, this is really really evil. So please don't do it unless you have a really good reason.
Now, assuming you do in fact have a good reason, the above solution should be good enough if all you are after is making sure you can use any class state information (methods and class attributes) from Kyle
or John
with RandomPerson
. However, as illustrated prior, RandomPerson
still isn't a true subclass of either.
Near as I can tell there is no way to actually randomly subclass an object's class at instance creation AND to have the class maintain state across multiple instance creations. You're going to have to fake it.
One way to fake it is to allow RandomPerson
to be considered a subclass of John
and Kyle
using the abstract baseclass module and __subclasshook__
, and adding that to your Person
class. This looks like it will be a good solution since the Person
class is an interface and isn't going to be directly used, anyway.
Here's a way to do that:
class Person(object):
__metaclass__ = abc.ABCMeta
def drive(self, f, t):
raise NotImplementedError
@classmethod
def __subclasshook__(cls, C):
if C.identity is cls:
return True
return NotImplemented
class John(Person):
def drive(self, f, t):
print "John drove from %s to %s" % (f,t)
class Kyle(Person):
def drive(self, f, t):
print "Kyle drove from %s to %s" % (f,t)
class RandomPerson(Person):
identity = None
def __new__(cls):
cls.identity = random.choice((John,Kyle))
new = super().__new__(cls)
new.__dict__.update(cls.identity.__dict__)
return new
>>> type(RandomPerson())
class RandomPerson
>>> rperson = RandomPerson()
>>> isinstance(rperson,John) or isinstance(rperson,Kyle)
True
Now RandomPerson
- though it technically is not a subclass - is considered to be a subclass of Kyle
or John
, and it also shares the state of Kyle
or John
. In fact, it will switch back and forth between the two, randomly, every time a new instance is created (or when RandomPerson.identity
is changed). Another effect of doing things this way: if you have multiple RandomPerson
instances, they all share the state of whatever RandomPerson
happens to be in that moment -- i.e., rperson1
might start out being Kyle
, and then when rperson2
is instantiated, both rperson2
AND rperson1
could be John
(or they could both be Kyle
and then switch to John
when rperson3
is created).
Needless to say, this is pretty weird behavior. In fact it is so weird, my suspicion is that your design needs a complete overhaul. I really don't think there is a very good reason to EVER do this (other than maybe playing a bad joke on someone).
If you don't want to mix this behavior into your Person
class, you could also do it separately:
class Person(object):
def drive(self, f, t):
raise NotImplementedError
class RandomPersonABC():
__metaclass__ = abc.ABCMeta
@classmethod
def __subclasshook__(cls, C):
if C.identity is cls:
return True
return NotImplemented
class John(Person, RandomPersonABC):
def drive(self, f, t):
print "John drove from %s to %s" % (f,t)
class Kyle(Person, RandomPersonABC):
def drive(self, f, t):
print "Kyle drove from %s to %s" % (f,t)
class RandomPerson(Person):
identity = None
def __new__(cls):
cls.identity = random.choice((John,Kyle))
new = super().__new__(cls)
new.__dict__.update(cls.identity.__dict__)
return new
You could just implement the RandomPerson
class to have a member called _my_driver
or whatever else you wanted. You would just call their drive
method from the RandomPerson.drive
method. It could look something like this:
class RandomPerson(Person):
# instantiate either John or Kyle, and inherit it.
def __init__(self):
self._my_person = John() if random.random() > 0.50 else Kyle()
def drive(self, f, t):
self._my_person.drive(f,t)
Alternatively, if you want to be more strict about making sure that the class has exactly the same methods as Kyle
or John
, you could set the method in the constructor like the following:
class RandomPerson(Person):
# instantiate either John or Kyle, and inherit it.
def __init__(self):
self._my_person = John() if random.random() > 0.50 else Kyle()
self.drive = self._my_person.drive
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