在Python中重新分类一个实例

我有一个由外部图书馆提供给我的课程。 我已经创build了这个类的一个子类。 我也有一个原始类的实例。

我现在想把这个实例变成我的子类的一个实例,而不改变实例已经有的属性(除了那些我的子类覆盖的属性)。

以下解决scheme似乎工作。

# 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. 

然而,我不相信这个解决scheme不包含我没有想到的任何警告(对于三重否定的抱歉),特别是因为重新分配神奇的__class__只是觉得不正确。 即使这样做,我也不禁要有这样的感觉。

在那儿?


编辑:谢谢大家的答案。 这是我从他们那里得到的:

  • 尽pipe通过分配__class__来重新分类实例的想法并不是一个广泛使用的习惯用法,但是大多数答案(撰写本文时为6分)认为这是一种有效的方法。 有一种说法(奥雅克)说,它“乍看起来很奇怪”,我同意这个观点(这是提问的原因)。 只有一个答案(由Jason Baker提供;有两个正面评论和投票)积极地阻止我这样做,但是基于示例用例而不是一般性的技术。

  • 没有一个答案,无论是否是正面的,在这个方法中find了实际的技术问题。 一个小例外是jls谁提到提防老式类,这可能是真实的,C扩展。 我认为新式的C语言扩展应该和Python本身一样好(假设后者是真的),但是如果你不同意,就保留答案。

至于pythonic这个问题,有几个正面的答案,但没有给出真正的理由。 看着禅( import this ),我猜在这种情况下最重要的规则是“显式比隐式更好”。 不过,我不确定这个规则是否支持或反对以这种方式重新分类。

  • 使用{has,get,set}attr似乎更加明确,因为我们正在明确地对对象进行更改,而不是使用魔法。

  • 使用__class__ = newclass似乎更明确,因为我们明确地说:“这现在是类'newclass的对象',期待不同的行为”,而不是静静地改变属性,而是让对象的用户相信他们正在处理旧的常规对象类。

总结:从技术angular度来看,这种方法似乎没有问题。 pythonicity的问题仍然没有得到答复,以“是”的偏见。

我接受了Martin Geisler的回答,因为Mercurial插件的例子是相当强大的(也因为它回答了我甚至还没有问过我自己的问题)。 不过,如果有关于pythonicity问题的争论,我还是想听听。 感谢所有迄今。

PS实际的用例是一个UI数据控件对象,需要在运行时增加额外的function。 但是,这个问题是非常普遍的。

当扩展(插件)想要改变表示本地存储库的对象时,在Mercurial (一个分布式版本控制系统)中重新分类这样的实例。 该对象被称为repo ,最初是一个localrepo实例。 它依次传递给每个扩展,并在需要时扩展将定义一个新类,它是repo.__class__的子类,并将repo更改为这个新的子类!

它在代码中看起来像这样 :

 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的模块级function。 Mercurial将在初始化扩展并调用ui (用户界面)和repo (存储库)参数时调用此方法。

该函数然后定义任何类别repo的子类。 简单地localrepo不够的,因为扩展需要能够相互扩展。 因此,如果第一个扩展将repo.__class__更改为foo_repo ,则下一个扩展应该将repo.__class__更改为foo_repo的子类,而不仅仅是localrepo的子类。 最后,这个函数改变了instance的类,就像你在代码中一样。

我希望这个代码可以显示这种语言function的合法使用。 我认为这是我见过它在野外使用的唯一地方。

我不确定在这种情况下使用inheritance是最好的(至less在“重新分类”方面)。 看起来你正走在正确的轨道上,但是听起来像是组合或者聚合对于这个是最好的。 下面是我想到的一个例子(在未经testing的伪代码中):

 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 

如果你不熟悉这个例子,你可能需要阅读更多关于集合和任意参数列表的知识 。

这可以。 我用过这个成语很多次。 要记住的一点是,这个想法不适合于旧式的类和各种C扩展。 通常这不是一个问题,但是因为你正在使用一个外部库,所以你只需要确保你没有处理任何旧式类或C扩展。

“状态模式允许一个对象在其内部状态改变时改变其行为,该对象似乎改变它的类。 – 首先devise模式。 非常相似的东西写Gamma et.al. 在他们的devise模式书。 (我在我的其他地方,所以没有报价)。 我认为这就是这种devise模式的重点。 但是,如果我可以在运行时更改对象的类,大部分时间我不需要该模式(有时候状态模式不仅仅是模拟类更改)。

另外,在运行时更改类并不总是工作:

 class A(object): def __init__(self, val): self.val = val def get_val(self): return self.val class B(A): def __init__(self, val1, val2): A.__init__(self, val1) self.val2 = val2 def get_val(self): return self.val + self.val2 a = A(3) b = B(4, 6) print a.get_val() print b.get_val() a.__class__ = B print a.get_val() # oops! 

除此之外,我还考虑在Pythonic运行时更改类并不时使用它。

呵呵,好玩的例子。

乍一看,“重新分类”是非常奇怪的。 那么“复制构造函数”方法呢? 你可以使用reflection(如hasattrgetattrsetattr来做到这一点。 这段代码将把一切从一个对象复制到另一个对象,除非它已经存在。 如果你不想复制方法,你可以排除它们; 看到评论if

 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) # if hasattr(value, '__call__'): # break # skip callables (ie functions) setattr(bar, attr, value) return bar 

所有这些reflection都不是很漂亮,但是有时候你需要一个丑陋的reflection机器来发生很酷的事情。 ;)

这个技术对我来说似乎是合理的Pythonic。 构图也是一个不错的select,但是分配给__class__是完全有效的(参见这里以稍微不同的方式使用它的配方)。

在ojrac的回答中, break突破了for -loop,并没有testing更多的属性。 我认为只要使用if语句来决定如何处理每个属性,并继续遍历所有属性的for -loop就更有意义了。 否则,我喜欢ojrac的答案,因为我也看到分配给__class__奇怪。 (我是一个Python的初学者,据我所知这是我第一篇文章到StackOverFlow。感谢所有伟大的信息!!)

所以我试图实现这一点。 我注意到dir()没有列出所有的属性。 http://jedidjah.ch/code/2013/9/8/wrong_dir_function/所以我添加了“; class ”,“ doc ”,“ module ”和“ init ”到要添加的列表中,如果它们不存在的话,(虽然他们可能都已经在那里了),并想知道是否有更多的东西丢失。 我也注意到,我说(有可能)分配给“ class ”之后说这很奇怪。

我会说这是完全正常的,如果它适合你。