【发布时间】:2010-11-02 17:01:14
【问题描述】:
我有一个由外部库提供给我的类。我创建了这个类的一个子类。我也有一个原始类的实例。
我现在想将此实例转换为我的子类的一个实例,而不更改该实例已有的任何属性(我的子类无论如何都会覆盖的属性除外)。
以下解决方案似乎有效。
# This class comes from an external library. I don't (want) to control
# it, and I want to be open to changes that get made to the class
# by the library provider.
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."
# This is my subclass.
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) + "."
# The questionable thing: Reclassing a programmer.
@classmethod
def teach_C(cls, programmer):
programmer.__class__ = cls # <-- do I really want to do this?
programmer.learn_C()
joel = C_Programmer("Joel")
joel.greet()
joel.hard_work()
#>Hi, my name is Joel.
#>I'll have to remember malloc and free and pointer arithmetic and curly braces.
jeff = Programmer("Jeff")
# We (or someone else) makes changes to the instance. The reclassing shouldn't
# overwrite these.
jeff._name = "Jeff A"
jeff.greet()
jeff.hard_work()
#>Hi, my name is Jeff A.
#>The garbage collector will take care of everything.
# Let magic happen.
C_Programmer.teach_C(jeff)
jeff.greet()
jeff.hard_work()
#>Hi, my name is Jeff A.
#>I'll have to remember malloc and free and pointer arithmetic and curly braces.
但是,我不相信这个解决方案不包含我没有想到的任何警告(对不起,三重否定),特别是因为重新分配神奇的 __class__ 感觉不对。即使这样可行,我也情不自禁地觉得应该有一种更 Python 的方式来做到这一点。
有吗?
编辑:感谢大家的回答。这是我从他们那里得到的:
虽然通过分配给
__class__来重新分类实例的想法不是一个广泛使用的习惯用法,但大多数答案(在撰写本文时 6 个中有 4 个)认为它是一种有效的方法。一个答案(由 ojrac 提供)说它“乍一看很奇怪”,我同意这一点(这是提出这个问题的原因)。只有一个答案(由 Jason Baker 提出;有两个正面的 cmets 和投票)积极劝阻我不要这样做,但是这样做是基于示例用例而不是一般技术。没有一个答案,无论是否正面,都发现此方法存在实际技术问题。一个小例外是 jls,他提到要提防旧式类(这很可能是真的)和 C 扩展。我认为这种新风格的类感知 C 扩展应该与 Python 本身一样好(假设后者是正确的),但如果您不同意,请继续提供答案。
关于这有多像pythonic的问题,有一些肯定的答案,但没有给出真正的理由。看看 Zen (import this),我想在这种情况下最重要的规则是“显式优于隐式”。不过,我不确定这条规则是支持还是反对以这种方式重新分类。
使用
{has,get,set}attr似乎更明确,因为我们明确地对对象进行更改而不是使用魔法。使用
__class__ = newclass似乎更明确,因为我们明确地说“现在这是类‘newclass’的对象,期待不同的行为”,而不是默默地改变属性,但让对象的用户相信他们正在处理旧类的常规对象。
总结:从技术角度来看,方法似乎还可以; pythonicity问题仍然没有答案,偏向于“是”。
我接受了 Martin Geisler 的回答,因为 Mercurial 插件示例是一个非常强大的示例(也因为它回答了一个我什至还没有问过自己的问题)。但是,如果对 pythonicity 问题有任何争论,我仍然想听听。到目前为止,谢谢。
附:实际用例是一个 UI 数据控件对象,在运行时需要增加额外的功能。但是,这个问题是非常笼统的。
【问题讨论】:
-
很好的例子。您能否解释一下在哪种情况下您觉得这种模式很有用?我喜欢这个主意,但我不确定它是否有用:)
-
@NicDumZ:谢谢你的提问;它在编辑中得到了回答。
-
+1 用于示例代码和对问题的广泛工作。很高兴看到。
-
为了记录,我不一定不鼓励使用重新分类。然而,它给了我(显然你在开始这个话题时)足够糟糕的感觉,我建议避免它,除非有令人信服的理由。请记住,Mercurial 在设计时就考虑到了这种类型的东西。你使用的任何库都可能不是。
-
谢谢杰森;我就是这样回答你的。我会记住这一点。到目前为止,自尝试以来我还没有遇到任何问题,但我会小心的。