我有一个由外部库提供给我的类。我已经创建了这个类的一个子类。我还有一个原始类的实例。
现在我想将这个实例转换成我的子类的实例,而不更改该实例已有的任何属性(除了那些我的子类重写的属性)。
下面的解决方案似乎有效。
# 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个)都认为这是一种有效的方法。一位anwswer(由ojrac撰写)说,这“乍一看很奇怪”,对此我表示同意(这是提出这个问题的原因)。只有一个答案(Jason Baker;给出了两个肯定的评论和投票)会让我积极地放弃这样做,但是这样做更多的是基于示例用例,而不是一般的技术。
无论答案是肯定的还是否定的,没有一个在这种方法中发现实际的技术问题。一个小的例外是jls提到要小心旧式类(很可能是真的)和C扩展。我认为新样式的类感知C扩展应该和Python本身一样好(假设后者是真的),但是如果您不同意,请继续回答。
至于这是怎样的Python,有一些积极的答案,但没有给出真正的理由。看看禅(import this
),我想在这种情况下最重要的规则是“显式优于隐式”。不过,我不确定这条规则是支持还是反对这样重分类。
使用{has,get,set}attr
似乎更为明确,因为我们正在显式地对对象进行更改,而不是使用magic。
使用__class__ = newclass
似乎更为明确,因为我们明确地说“这现在是‘newclass’类的对象,期望不同的行为”,而不是默默地更改属性,而是让对象的用户相信他们正在处理旧类的常规对象。
总结:从技术的角度来看,这种方法似乎是可行的;pythonicity问题仍然没有得到回答,而且偏向于“是”
我已经接受了Martin Geisler的答案,因为Mercurial插件的例子是一个非常强大的例子(也因为它回答了一个我甚至还没有问过自己的问题)。不过,如果在python问题上有任何争论,我还是很想听听。谢谢大家。
p.S.实际用例是一个UI数据控制对象,需要在运行时增加额外的功能。不过,这个问题本来是很笼统的。
这很好。我用这个成语很多次了。但要记住的一件事是,这种想法在旧式类和各种C扩展中并不适用。通常这不是问题,但是由于您使用的是外部库,因此您只需确保不处理任何旧样式的类或C扩展。
当扩展(插件)想要更改表示本地存储库的对象时,在Mercurial(一个分布式修订控制系统)中完成这样的实例重分类。对象名为
repo
,最初是一个localrepo
实例。它依次传递给每个扩展,并且在需要时,扩展将定义一个新类,该类是repo.__class__
的子类,将repo
的类更改为这个新的子类!它在代码中看起来是like this:
扩展(我从bookmarks扩展中获取代码)定义了一个模块级函数
reposetup
。Mercurial将在初始化扩展时调用此函数,并传递一个ui
(用户界面)和repo
(存储库)参数。然后,该函数定义一个类
repo
的子类。仅仅对localrepo
进行子类划分是不够的,因为扩展需要能够相互扩展。因此,如果第一个扩展将repo.__class__
更改为foo_repo
,那么下一个扩展应该将repo.__class__
更改为foo_repo
的子类,而不仅仅是localrepo
的子类。最后,该函数更改实例的类,就像您在代码中所做的那样。我希望这段代码能显示这一语言特性的合法使用。我想这是我见过它在野外使用的唯一地方。
我不确定在这种情况下使用继承是最好的(至少在“重新分类”方面)。看起来你走对了,但听起来组合或聚合是最好的选择。下面是我正在考虑的一个例子(在未经测试的伪代码中):
如果您不熟悉sets和arbitrary argument lists以获取此示例,则可能需要阅读更多有关它们的信息。
相关问题 更多 >
编程相关推荐