自动将对象从类转换为子类

时间:2018-09-25 14:22:43

标签: python class object subclass

我必须注意以下问题。我有一个数据输入,其中定义了一个类型(在下面的示例中为动物)。基于此类型,我需要不同的子类,因为我希望基于类型具有不同的属性。这是一个示例:

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 :(得分:1)

您想要的东西有时称为虚拟构造函数,因为子类实例是由基类构造函数创建的。这通常通过使用某种“工厂”功能来处理。

但是,我对大多数工厂函数实现不喜欢的一件事是,它们的实现方式通常是每次将另一个子类添加到工厂函数时都需要手动修改工厂函数。类层次结构。更好的实现可以将其减少为只调用一个其他“辅助”函数来注册每个子类。

在Python中,可以通过重写基类的默认__new__()方法来实现此功能(有效地使其成为静态工厂功能)。然后,在该方法内,可以使用类对象的__subclasses__()方法来查找它们,而无需首先手动调用某些“注册”帮助程序方法。因此,在虚拟构造的类层次结构中添加子类几乎是自动的。

这是将这些概念应用于问题中的示例类的方法。还要注意,我也修改了您的代码,因此它更紧密地遵循PEP 8 - Style Guide for Python Code准则。

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

这基本上只是我对another question的回答中的代码的改编。

答案 1 :(得分:1)

您可以为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

答案 2 :(得分:1)

首先,不要只绕过dict秒;隐藏实际所需的参数,并丑化代码。为每个初始化器上识别的参数使用常规名称,将其余名称捕获为**kwargs并将它们传递给初始化器链。

第二,要实现您的目标,请在classmethod上将备用构造函数设为Pet,然后使用它。 classmethod可以返回一个新对象,并且它们不限于对像__init__这样的已创建对象进行操作(__new__可以代替{{1 }}可以达到类似的效果,但是比较复杂,而且通常不太明显):

__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)

答案 3 :(得分:0)

假设对象中具有类型的str(以您的情况为例):

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


mypet1 = pet_factory(pet1)

不确定全局变量是否适合使用tbh