Фабрика классов Python наследует случайного родителя
У меня есть такой код:
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"
Как я могу реализовать RandomPerson, со следующими требованиями:
- призвание
person = RandomPerson()
должен вернутьRandomPerson
объект. RandomPerson
должен подкласс либоJohn
или жеKyle
случайным образом.
3 ответа
В своем первоначальном ответе (который я удалил, потому что это было просто неправильно), я сказал, что подумал бы сделать это так:
class RandomPerson(Person):
def __init__(self):
rand_person = random.choice((John, Kyle))()
self.__dict__ = rand_person.__dict__
Этот способ является адаптацией языка Python Borg; идея заключалась в том, что все, что имеет значение для объекта, содержится в его __dict__
,
Однако это работает только при перезаписи объектов одного класса (что вы делаете в идиоме Борг); предмет __dict__
содержит только информацию о состоянии, относящуюся к экземпляру объекта, но не класс объекта.
Можно переключить класс объекта следующим образом:
class RandomPerson(Person):
def __init__(self):
rand_person = random.choice((John, Kyle))
self.__class__ = rand_person
Тем не менее, делать это таким образом будет означать, что призыв кRandomPerson
тогда бы не вернуть экземплярRandomPerson
согласно вашему требованию, но Kyle
или из John
, Так что это не пойдет.
Вот способ получить RandomPerson
объект, которыйдействует как Kyle
или жеJohn
, но не:
class RandomPerson(Person):
def __new__(cls):
new = super().__new__(cls)
new.__dict__.update(random.choice((Kyle,John)).__dict__)
return new
Этот - очень похожий на идиому Борг, за исключением того, что мы делаем это с классами вместо объектов экземпляров, а мы только копируемтекущую версию выбранного класса dict - действительно довольно злой: мы лоботомизировали RandomPerson
класс и (случайно) застрял мозгиKyle
или же John
класс на месте. И нет никаких признаков, к сожалению, что это произошло:
>>> rperson = RandomPerson()
>>> assert isinstance(rperson,Kyle) or isinstance(rperson,John)
AssertionError
Таким образом, мыдо сих пор не подкласс Kyle
или жеJohn
, Кроме того, это действительно действительно зло. Поэтому, пожалуйста, не делайте этого, если у вас нет действительно веской причины.
Теперь, если у вас действительно есть веская причина, вышеприведенное решение должно быть достаточно хорошим, если все, что вам нужно, это убедиться, что вы можете использовать любую информацию о состоянии класса (методы и атрибуты класса) из Kyle
или жеJohn
с RandomPerson
, Однако, как показано ранее, RandomPerson
все еще не настоящий подкласс ни того, ни другого.
Насколько я могу судить, невозможно создать случайный подкласс класса объекта при создании экземпляра И сохранить класс в состоянии нескольких экземпляров. Тебе придётся подделать это.
Один из способов подделать это позволить RandomPerson
считаться подклассомJohn
а также Kyle
используя абстрактный модуль базового класса и __subclasshook__
и добавив это к вашему Person
учебный класс. Похоже, это будет хорошим решением, так какPerson
класс - это интерфейс, и он не будет использоваться напрямую.
Вот способ сделать это:
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
СейчасRandomPerson
- хотя технически это не подкласс -считается подклассом Kyle
или жеJohn
и это также разделяет состояниеKyle
или жеJohn
, Фактически, он будет переключаться между двумя случайным образом каждый раз, когда создается новый экземпляр (или когда RandomPerson.identity
изменено). Еще один эффект такой работы: если у вас есть несколько RandomPerson
случаи, они все разделяют состояние любогоRandomPerson
случается в тот момент - т.е. rperson1
может начать быть Kyle
и когда rperson2
инстанцируется, оба rperson2
А ТАКЖЕ rperson1
может быть John
(или они оба могут быть Kyle
а затем переключиться на John
когда rperson3
создано).
Излишне говорить, что это довольно странное поведение. На самом деле это так странно, я подозреваю, что ваш дизайн нуждается в полной переработке. Я действительно не думаю, что есть очень веская причина КОГДА-ЛИБО делать это (кроме, может быть, шутки с кем-то).
Если вы не хотите смешивать это поведение в Person
класс, вы также можете сделать это отдельно:
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
Вы могли бы просто реализовать RandomPerson
класс, чтобы иметь член под названием _my_driver
или что еще вы хотели. Вы бы просто назвали их drive
метод из RandomPerson.drive
метод. Это может выглядеть примерно так:
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)
В качестве альтернативы, если вы хотите быть более строгим, чтобы убедиться, что класс имеет точно такие же методы, как Kyle
или же John
Вы можете установить метод в конструкторе следующим образом:
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
В своем последнем комментарии к моему другому ответу вы сказали:
Я собираюсь изменить класс объекта, как вы указали:
rand_person = random.choice((John, Kyle))
а такжеself.__class__ = rand_person
, Я переехал методыRandomPerson
Вернуться вPerson
, а такжеRandomPerson
теперь работает как класс фабричного производства.
Если я могу так сказать, это странный выбор. Прежде всего, замена класса после создания объекта не выглядит слишком питонно (это слишком сложно). Было бы лучше генерировать экземпляр объекта случайным образом, а не присваивать класс по факту:
class RandomPerson(): # Doing it this way, you also don't need to inherit from Person
def __new__(self):
return random.choice((Kyle,John))()
Во-вторых, если код был реорганизован так, что вам больше не требуется RandomPerson
объект, зачем вообще его иметь? Просто используйте заводскую функцию:
def RandomPerson():
return random.choice((Kyle,John))()