在Python中重新分类实例

bal*_*pha 56 python subclass

我有一个由外部库提供给我的课程.我已经创建了这个类的子类.我也有一个原始类的实例.

我现在想要将此实例转换为我的子类的实例,而不更改实例已有的任何属性(除了我的子类覆盖的那些属性).

以下解决方案似乎有效.

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

如果您不熟悉它们,可能必须阅读有关集合任意参数列表的更多信息才能获得此示例.

  • +1为构图模式.这里最有意义. (2认同)
  • 在处理外部API提供的对象时,+1 Wrapping总是更好. (2认同)