在 Python 中,实例的分类通常是指将一个对象从一个类切换到另一个类。Python 不允许直接更改对象的类,但有一些间接方法可以实现类似的效果。

Python中对实例进行重新分类_Python

1、问题背景

在编写Python程序时,您可能会遇到这样的情况:您有一个由外部库提供的类,并且您已经创建了该类的子类。现在,您希望将该类的实例转换为您子类的实例,而无需更改该实例已经具有的任何属性(除了您的子类覆盖的属性)。

为了解决这个问题,您可能考虑使用以下代码:

class Programmer(object):
    def __init__(self,name):
        self._name = name

    def greet(self):
        print "Hi, my name is %s." % self._name

    def hard_work(self):
        print "The garbage collector will take care of everything."

class C_Programmer(Programmer):
    def __init__(self, *args, **kwargs):
        super(C_Programmer,self).__init__(*args, **kwargs)
        self.learn_C()

    def learn_C(self):
        self._knowledge = ["malloc","free","pointer arithmetic","curly braces"]

    def hard_work(self):
        print "I'll have to remember " + " and ".join(self._knowledge) + "."

    @classmethod
    def teach_C(cls, programmer):
        programmer.__class__ = cls
        programmer.learn_C()

joel = C_Programmer("Joel")
joel.greet()
joel.hard_work()

jeff = Programmer("Jeff")
jeff._name = "Jeff A" 
jeff.greet()
jeff.hard_work()
C_Programmer.teach_C(jeff)
jeff.greet()
jeff.hard_work()

然而,您可能对这种解决方案是否包含您没想到的任何缺陷感到担忧,特别是重新分配神奇的class似乎并不合适。即使这样做有效,您也无法摆脱一种感觉,认为应该有更 Pythonic 的方法来实现此目的。

2、解决方案

以下是几种可能更 Pythonic 的解决方案:

  • 使用getattr、setattr和hasattr

以下代码使用getattr、setattr和hasattr来复制一个对象的所有属性到另一个对象,除非该属性已经存在:

class Foo(object):
    def __init__(self):
        self.cow = 2
        self.moose = 6

class Bar(object):
    def __init__(self):
        self.cat = 2
        self.cow = 11

    def from_foo(foo):
        bar = Bar()
        attributes = dir(foo)
        for attr in attributes:
            if (hasattr(bar, attr)):
                break
            value = getattr(foo, attr)
            setattr(bar, attr, value)

        return bar

foo = Foo()
bar = Bar.from_foo(foo)

print(bar.cow)  # 2
print(bar.moose)  # 6
  • 使用copy.copy

以下代码使用copy.copy来复制一个对象的所有属性到另一个对象:

import copy

class Foo(object):
    def __init__(self):
        self.cow = 2
        self.moose = 6

class Bar(object):
    def __init__(self):
        self.cat = 2
        self.cow = 11

foo = Foo()
bar = copy.copy(foo)

print(bar.cow)  # 2
print(bar.moose)  # 6
  • 使用functools.update_wrapper

以下代码使用functools.update_wrapper来更新一个函数的class属性:

import functools

class Foo(object):
    def __init__(self):
        self.cow = 2
        self.moose = 6

def foo_method(self):
    print("I am a foo method.")

class Bar(object):
    def __init__(self):
        self.cat = 2
        self.cow = 11

def bar_method(self):
    print("I am a bar method.")

def update_class(func, cls):
    functools.update_wrapper(func, cls.__dict__)
    func.__class__ = cls
    return func

foo = Foo()
bar = Bar()

foo_method = update_class(foo_method, Bar)
bar_method = update_class(bar_method, Foo)

foo.foo_method()  # I am a bar method.
bar.bar_method()  # I am a foo method.
  • 使用typing.cast

以下代码使用typing.cast来将一个对象转换为另一个类型:

from typing import cast

class Foo(object):
    def __init__(self):
        self.cow = 2
        self.moose = 6

class Bar(object):
    def __init__(self):
        self.cat = 2
        self.cow = 11

foo = Foo()
bar = cast(Bar, foo)

print(bar.cow)  # 11
print(bar.moose)  # AttributeError: 'Bar' object has no attribute 'moose'

这些解决方案都比重新分配class更 Pythonic,并且都能将一个对象的实例转换为另一个类型的实例。

总结

  • 修改 __class__ 是一种直接但潜在危险的方式,不推荐在复杂场景下使用。
  • 复制属性到新实例是更安全的方法,适用于大多数场景。
  • 使用工厂方法或多态可以更优雅地解决实例分类问题,适合设计模式驱动的开发。
  • 如果需要频繁切换,可以使用动态代理或组合设计实现行为变更。