我有一个由外部库提供给我的课程.我已经创建了这个类的子类.我也有一个原始类的实例.
我现在想要将此实例转换为我的子类的实例,而不更改实例已有的任何属性(除了我的子类覆盖的那些属性).
以下解决方案似乎有效.
# 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.
Run Code Online (Sandbox Code Playgroud)
但是,我不相信这个解决方案不包含任何我没有想过的警告(对于三重否定而感到抱歉),特别是因为重新分配魔法__class__只是感觉不对.即使这样有效,我也不禁感觉应该有更多的pythonic方法.
在那儿?
编辑:谢谢大家的回答.以下是我从他们那里得到的:
尽管通过赋值重新分类实例的想法__class__并不是一个广泛使用的习惯用法,但大多数答案(在撰写本文时为6个中的4个)认为它是一种有效的方法.一个anwswer(由ojrac说)乍一看"非常奇怪",我同意(这是提出问题的原因).只有一个答案(由Jason Baker提出;有两个正面评论和投票)积极阻止我这样做,不过这样做基于示例用例而不是一般的技术.
无论是否正面,这些答案都没有在这种方法中找到实际的技术问题.一个小例外是jls提到提防旧式类,这可能是真的,并且C扩展.我认为新方法感知的C扩展应该与Python本身一样好(假设后者是真的),尽管如果你不同意,请保持答案.
至于pythonic是如何的问题,有一些积极的答案,但没有给出真正的理由.看看Zen(import this),我猜这个案例中最重要的规则是"明确比隐含更好".不过,我不确定这条规则是否反对以这种方式重新分类.
使用{has,get,set}attr似乎更明确,因为我们明确地对对象进行更改而不是使用魔法.
使用__class__ = newclass似乎更明确,因为我们明确地说"这现在是类'newclass的对象,'期望一个不同的行为"而不是默默地改变属性,但让对象的用户相信他们正在处理旧类的常规对象.
总结:从技术角度来看,该方法似乎没问题; pythonicity问题仍然没有答案,偏向于"是".
我已经接受了Martin Geisler的回答,因为Mercurial插件示例非常强大(并且还因为它回答了我甚至还没有问过自己的问题).但是,如果对pythonicity问题有任何争论,我仍然希望听到它们.谢谢所有人.
PS实际用例是一个UI数据控件对象,需要在运行时增加其他功能.但是,这个问题应该是非常笼统的.
Mar*_*ler 19
当扩展(插件)想要更改表示本地存储库的对象时,重新分类这样的实例是在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
Run Code Online (Sandbox Code Playgroud)
扩展(我从书签扩展中获取代码)定义了一个名为的模块级函数reposetup.Mercurial将在初始化扩展并调用ui(用户界面)和repo(存储库)参数时调用此方法.
然后,该函数定义了任何类的子类repo.这将不足够简单地继承localrepo,因为扩展需要能够延长对方.因此,如果第一个扩展名更改repo.__class__为foo_repo,则下一个扩展名应更改repo.__class__为子类,foo_repo而不仅仅是子类localrepo.最后,该函数更改了instanceø的类,就像您在代码中所做的那样.
我希望这段代码可以合法地使用这种语言功能.我认为这是我见过它在野外使用的唯一地方.
Jas*_*ker 11
在这种情况下,我不确定继承的使用是否最好(至少在"重新分类"方面).看起来你是在正确的轨道,但听起来像组合或聚合将是最好的.这是我正在考虑的一个例子(在未经测试的伪esque代码中):
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
Run Code Online (Sandbox Code Playgroud)
如果您不熟悉它们,可能必须阅读有关集合和任意参数列表的更多信息才能获得此示例.
| 归档时间: |
|
| 查看次数: |
9272 次 |
| 最近记录: |