问题
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:
- calling
person = RandomPerson()
must return aRandomPerson
object. RandomPerson
should subclass eitherJohn
orKyle
randomly.
回答1:
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
回答2:
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
回答3:
In your most recent comment on my other answer you said:
I'm gonna go with changing the class of the object, like you pointed out:
rand_person = random.choice((John, Kyle))
andself.__class__ = rand_person
. I've moved the methods ofRandomPerson
back intoPerson
, andRandomPerson
works now like a factory-ish generating class.
If I may say so, this is an odd choice. First of all, swapping out the class after object creation doesn't seem very pythonic (it's overly complicated). It would be better to generate the object instance randomly instead of assigning the class after the fact:
class RandomPerson(): # Doing it this way, you also don't need to inherit from Person
def __new__(self):
return random.choice((Kyle,John))()
Secondly, if the code has been refactored so that you no longer require a RandomPerson
object, why have one at all? Just use a factory function:
def RandomPerson():
return random.choice((Kyle,John))()
来源:https://stackoverflow.com/questions/30899499/python-class-factory-inherit-random-parent