我有一个由外部库提供给我的类。我已经创建了这个类的子类。我还有一个原始类的实例。
现在,我希望将这个实例转换为我的子类的一个实例,而不更改该实例已经拥有的任何属性(除了那些被我的子类覆盖的属性)。
下面的解决方案似乎是可行的。
# 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__
感觉不正确。即使这样能行得通,我也忍不住觉得应该有更多的pythonic方式来做这件事。
在那里吗?
编辑:感谢大家的回答。这是我从他们那里得到的:
__class__
来重分类实例的想法并不是一个广泛使用的习惯用法,但大多数答案(在撰写本文时6个答案中有4个)认为这是一种有效的方法。一个回答者(作者ojrac)说“乍一看很奇怪”,我同意(这就是问这个问题的原因)。只有一个答案(由Jason Baker提供;有两个积极的评论和投票)积极地阻止我这样做,然而,这样做更多是基于示例用例,而不是一般的技术。至于这个问题有多严重,有一些肯定的答案,但没有给出真正的原因。看看禅宗(import this
),我想在这种情况下最重要的规则是“显式比隐式更好”。不过,我不确定这条规则是支持还是反对以这种方式重新分类。
使用{has,get,set}attr
的
__class__ = newclass
的总结:从技术角度来看,该方法似乎还可以;pythonicity问题仍然没有得到回答,偏向于“是”。
我接受了Martin Geisler的回答,因为Mercurial插件的例子非常强大(也因为它回答了一个我甚至还没有问过自己的问题)。但是,如果在pythonicity问题上有任何争论,我仍然很愿意听到。到目前为止,感谢所有人。
附注:实际的用例是一个UI数据控件对象,它需要在运行时增加额外的功能。然而,这个问题应该是非常笼统的。
发布于 2009-06-13 18:47:45
当扩展(插件)想要更改代表本地存储库的对象时,在Mercurial (一个分布式修订控制系统)中就可以像这样对实例进行重新分类。该对象名为repo
,最初是一个localrepo
实例。它被依次传递给每个扩展,当需要时,扩展将定义一个新类,它是repo.__class__
的子类,并将repo
的类更改为这个新的子类!
它在代码中看起来像like this:
def reposetup(ui, repo):
# ...
class bookmark_repo(repo.__class__):
def rollback(self):
if os.path.exists(self.join('undo.bookmarks')):
util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
return super(bookmark_repo, self).rollback()
# ...
repo.__class__ = bookmark_repo
这个扩展(我取自书签扩展的代码)定义了一个名为reposetup
的模块级函数。Mercurial将在初始化扩展并传递ui
(用户界面)和repo
(存储库)参数时调用此方法。
然后,该函数定义了repo
类的子类。仅仅对localrepo
进行子类是不够的,因为扩展需要能够相互扩展。因此,如果第一个扩展将repo.__class__
更改为foo_repo
,则下一个扩展应将repo.__class__
更改为foo_repo
的子类,而不仅仅是localrepo
的子类。最后,该函数更改实例ø的类,就像您在代码中所做的那样。
我希望这段代码能展示这种语言功能的合法使用。我想这是我唯一见过在野外使用它的地方。
发布于 2009-06-13 15:31:04
我不确定在这种情况下使用继承是最好的(至少在“重分类”方面是这样)。看起来你是在正确的轨道上,但听起来组合或聚合将是最好的。下面是我正在考虑的一个例子(用未经测试的伪风格代码):
from copy import copy
# As long as none of these attributes are defined in the base class,
# this should be safe
class SkilledProgrammer(Programmer):
def __init__(self, *skillsets):
super(SkilledProgrammer, self).__init__()
self.skillsets = set(skillsets)
def teach(programmer, other_programmer):
"""If other_programmer has skillsets, append this programmer's
skillsets. Otherwise, create a new skillset that is a copy
of this programmer's"""
if hasattr(other_programmer, skillsets) and other_programmer.skillsets:
other_programmer.skillsets.union(programmer.skillsets)
else:
other_programmer.skillsets = copy(programmer.skillsets)
def has_skill(programmer, skill):
for skillset in programmer.skillsets:
if skill in skillset.skills
return True
return False
def has_skillset(programmer, skillset):
return skillset in programmer.skillsets
class SkillSet(object):
def __init__(self, *skills):
self.skills = set(skills)
C = SkillSet("malloc","free","pointer arithmetic","curly braces")
SQL = SkillSet("SELECT", "INSERT", "DELETE", "UPDATE")
Bob = SkilledProgrammer(C)
Jill = Programmer()
teach(Bob, Jill) #teaches Jill C
has_skill(Jill, "malloc") #should return True
has_skillset(Jill, SQL) #should return False
如果您不熟悉sets和arbitrary argument lists,则可能需要阅读有关它们的更多信息才能获得此示例。
发布于 2009-06-13 18:13:47
这很好。这个成语我已经用过很多次了。不过,要记住的一件事是,这种想法与老式类和各种C扩展不能很好地配合。通常这不是问题,但由于您使用的是外部库,因此您只需确保您不会处理任何旧式的类或C扩展。
https://stackoverflow.com/questions/990758
复制相似问题