【问题标题】:convert object from class to subclass automatically自动将对象从类转换为子类
【发布时间】:2018-09-25 14:22:42
【问题描述】:

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

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)

我想根据类型参数自动将宠物对象转换为狗或猫。最后一点至关重要,因为会有很多宠物,我无法手动读取类型并显式使用相应的子类。 有没有办法做到这一点?

提前致谢

【问题讨论】:

  • 例如,type(mypet1) 就是 dog
  • 是的。你是对的。

标签: python class object subclass


【解决方案1】:

首先,不要只是传递dicts;它隐藏了实际需要的参数,并使代码变得丑陋。对每个初始化器上识别的参数使用常规名称,将其余的捕获为 **kwargs 并将它们传递到初始化器链。

其次,为了实现您的目标,在Pet 上创建一个备用构造函数作为classmethod 并使用它。 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

【讨论】:

  • 如果类型变量不是立即给出,而是通过函数确定并通过 self.type 存储在对象中,这将如何改变?在这种情况下,我无法检查 fromtype 类方法中的类型参数还是我错了?
  • @Daniel:在一般情况下,不能安全地追溯更改类型。如果您可以在fromtype 调用期间确定self.type 值,那没关系(您只需停止接收type 作为参数并在fromtype 中计算它),但没有明智的方法来更改类已经构建的实例。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

    然而,对于大多数工厂函数实现,我喜欢的一件事是,它们的实现方式通常需要在每次将另一个子类添加到类层次结构。更好的实现可以将其简化为只需一次调用其他“帮助”函数来注册每个子类。

    在 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 的回答中代码的改编。

    【讨论】:

    • 非常感谢您的详细解答。你能告诉我行 class UnknownKind(Exception): pass 是做什么的或为什么需要它吗?
    • 丹尼尔:当然。这是一个嵌套在Pet 基类中的自定义异常类。它不一定要在那里,但这样做会使它在哪里被使用变得很明显。拥有一个自定义的Exception 类使您可以轻松地在except 子句中明确地处理它们。我将在我的答案中添加一个使用它的示例。
    【解决方案4】:

    假设您在对象中有 str 类型(在您的案例类型中):

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

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

    【讨论】:

    • 这将返回一个新的子类,而不是一个子类的实例。此外,pet_obj dict 中的键将成为新子类的类变量,而不是 OP 想要的新实例的实例属性。
    • 不:请在评论之前尝试代码,这将在他的 DICTS mypet1 = pet_factory(pet1) mypet1.weight Out[250]: 100 mypet Out[ 248]:.dog
    • 这正是我的意思。 mypet1 现在持有对类__main__.dog 的引用,而不是类__main__.dog 的实例。输入type(mypet1) 看看我的意思。
    • 什么时候必须通过任何类型的数据操作来定义类型?那么当我没有将类型作为预定义字符串时呢?
    • 然后使用较旧的答案,它将创建类然后实例化它(使用类型、类名并传递 kwargs)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-09-03
    • 1970-01-01
    • 2022-09-29
    • 2016-09-24
    • 2012-07-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多