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