преобразовать объект из класса в подкласс автоматически - PullRequest
0 голосов
/ 25 сентября 2018

У меня к следующей проблеме.У меня есть ввод данных, в котором определен тип (животное в следующем примере).Основываясь на этом типе, мне нужны разные подклассы, потому что я хочу иметь разные атрибуты, основанные на типе.Вот пример:

class pet:
    def __init__(self, dict):
        self.name = dict['name']
        self.type = dict['type']


class dog(pet):
    def __init__(self, dict):
        pet.__init__(self, dict)
        self.weight = dict['weight']


class cat(pet):
    def __init__(self, dict):
        pet.__init__(self, dict)
        self.color = dict['color']


if __name__ == '__main__':
    pet1 = {'name': 'Harry', 'type': 'dog', 'weight': 100}
    pet2 = {'name': 'Sally', 'type': 'cat', 'color': 'blue'}

    mypet1 = pet(pet1)
    mypet2 = pet(pet2)

Я хотел бы преобразовать объекты питомца в собаку или кошку, соответственно, на основе аргумента типа автоматически.Последний пункт имеет решающее значение, так как будет много домашних животных, и я не могу прочитать тип вручную и явно использовать соответствующий подкласс.Есть ли способ сделать это?

Заранее спасибо

Ответы [ 4 ]

0 голосов
/ 25 сентября 2018

Во-первых, не просто проходите мимо dict с;это скрывает фактические требуемые аргументы и ухудшает код.Используйте обычные имена для аргументов, распознаваемых на каждом инициализаторе, запишите остальные как **kwargs и передайте их по цепочке инициализаторов.

Во-вторых, для достижения вашей цели создайте альтернативный конструктор как classmethod на * 1006.* и используйте это.classmethod могут возвращать новый объект, и они не ограничены работой с уже созданным объектом, таким как __init__ (__new__ может заменить __init__ для достижения аналогичного эффекта, ноэто более неудобно и, как правило, менее очевидно):

class pet:
    def __init__(self, name, type):
        self.name = name
        self.type = type

    @classmethod
    def fromtype(cls, type, **kwargs):
        for c in cls.__subclasses__():
            if c.__name__ == type:
                break
        else:
            raise ValueError("Unknown type: {!r}".format(type))
        return c(type=type, **kwargs)

class dog(pet):
    def __init__(self, weight, **kwargs):
        pet.__init__(self, **kwargs)
        self.weight = weight


class cat(pet):
    def __init__(self, color, **kwargs):
        pet.__init__(self, **kwargs)
        self.color = color

Использование меняется незначительно: от:

mypet1 = pet(pet1)
mypet2 = pet(pet2)

до:

mypet1 = pet.fromtype(**pet1)
mypet2 = pet.fromtype(**pet2)

и когда вам нужночтобы создать объект напрямую, вы можете передать нормальные аргументы обычному конструктору вместо создания dict, который в противном случае не используется.

0 голосов
/ 25 сентября 2018

То, что вы хотите, иногда называется виртуальным конструктором , потому что экземпляры подкласса создаются конструктором базового класса.Это часто выполняется с помощью какой-то «фабричной» функции.

Однако, одна вещь, которую я не люблю в большинстве реализаций фабричных функций, состоит в том, что они часто реализуются таким образом, что фабричная функция требует ручного изменения при каждом добавлении другого подкласса.к иерархии классов.В лучших реализациях это может быть сведено к одному вызову некоторой другой «вспомогательной» функции для регистрации каждого подкласса.

В Python такая функция может быть реализована путем переопределения метода по умолчанию __new__() базового класса (фактически делая егостатическая заводская функция).Затем в этом методе можно использовать метод __subclasses__() объекта класса, чтобы найти их все, не требуя ручного вызова какого-либо вспомогательного метода «register».Таким образом, добавление подкласса к виртуально сконструированной иерархии классов происходит в основном автоматически.

Вот как применить эти понятия к классам примеров в вашем вопросе.Также обратите внимание, что я также изменил ваш код, чтобы он более точно соответствовал PEP 8 - Руководству по стилю для кода Python .

class Pet:
    class UnknownType(Exception): pass  # Custom Exception subclass.

    def __init__(self, dictionary):
        self.name = dictionary['name']
        self.type = dictionary['type']

    @classmethod
    def _get_all_subclasses(cls):
        """ Recursive generator of all subclasses of a class. """
        for subclass in cls.__subclasses__():
            yield subclass
            for subclass in subclass._get_all_subclasses():
                yield subclass

    def __new__(cls, dictionary):
        """ Create instance of appropriate subclass using string
            value of 'type' in dictionary.
        """
        kind = dictionary['type']

        for subclass in cls._get_all_subclasses():
            if subclass.kind == kind:
                # Using "object" base class method avoids recursion here.
                return object.__new__(subclass)
        else:  # no subclass with matching type found.
            raise Pet.UnknownType(
                'type "{}" is not recognized'.format(kind))


class Dog(Pet):
    kind = 'Dog'

    def __init__(self, dictionary):
        super().__init__(dictionary)
        self.weight = dictionary['weight']


class Cat(Pet):
    kind = 'Cat'

    def __init__(self, dictionary):
        super().__init__(dictionary)
        self.color = dictionary['color']


if __name__ == '__main__':
    pet1 = {'name': 'Harry', 'type': 'Dog', 'weight': 100}
    pet2 = {'name': 'Sally', 'type': 'Cat', 'color': 'blue'}
    pet3 = {'name': 'Joe', 'type': 'Frog', 'eyecolor': 'brown'}

    mypet1 = Pet(pet1)
    mypet2 = Pet(pet2)

    print(mypet1.__class__.__name__)  # -> Dog
    print(mypet2.__class__.__name__)  # -> Cat

    # Example showing use of custom Exception subclass.
    try:
        mypet3 = Pet(pet3)
    except Pet.UnknownType as exc:
        print('Error occurred:', exc)
        # -> Error occurred: type "Frog" is not recognized

По сути, это всего лишь адаптацияКод в моем ответе на другой вопрос .

0 голосов
/ 25 сентября 2018

Вы можете создать метод класса для pet, который будет перебирать свои подклассы, чтобы найти тот, имя которого соответствует данному type, а затем создать экземпляр подкласса с заданным атрибутом dict:

class pet:
    @classmethod
    def get_pet(cls, attributes):
        for c in cls.__subclasses__():
            if c.__name__ == attributes['type']:
                return c(attributes)

так что

dog = pet.get_pet(pet1)
print(dog.__class__.__name__, dog.name, dog.type, dog.weight)

выведет:

dog Harry dog 100
0 голосов
/ 25 сентября 2018

Предполагая, что у вас есть str типа в объекте (в вашем случае):

def pet_factory(pet_obj):
    return globals()[pet_obj['type']](pet_obj)


mypet1 = pet_factory(pet1)

Не уверен, правильно ли использовать глобальные переменные tbh

...