Фабрика классов 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))()
Другие вопросы по тегам