首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >在Python中对实例进行重分类

在Python中对实例进行重分类
EN

Stack Overflow用户
提问于 2009-06-13 14:17:42
回答 8查看 10K关注 0票数 58

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

现在,我希望将这个实例转换为我的子类的一个实例,而不更改该实例已经拥有的任何属性(除了那些被我的子类覆盖的属性)。

下面的解决方案似乎是可行的。

代码语言:javascript
复制
# 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提供;有两个积极的评论和投票)积极地阻止我这样做,然而,这样做更多是基于示例用例,而不是一般的技术。
  • 所有的答案,无论是肯定的还是否定的,都不会在这种方法中发现实际的技术问题。一个小的例外是jls,他提到要提防老式类和C扩展。我认为新风格的类感知的C扩展应该和Python本身一样适用于这种方法(假设后者是真的),尽管如果您不同意,请继续给出答案。

至于这个问题有多严重,有一些肯定的答案,但没有给出真正的原因。看看禅宗(import this),我想在这种情况下最重要的规则是“显式比隐式更好”。不过,我不确定这条规则是支持还是反对以这种方式重新分类。

使用{has,get,set}attr

  • 似乎更明确,因为我们显式地对对象进行更改,而不是使用魔法。使用__class__ = newclass
  • 似乎更明确,因为我们显式地说“这现在是一个'newclass‘类的对象,’期望不同的行为‘”,而不是默默地更改属性,而是让对象的用户认为他们正在处理旧类的常规对象。

总结:从技术角度来看,该方法似乎还可以;pythonicity问题仍然没有得到回答,偏向于“是”。

我接受了Martin Geisler的回答,因为Mercurial插件的例子非常强大(也因为它回答了一个我甚至还没有问过自己的问题)。但是,如果在pythonicity问题上有任何争论,我仍然很愿意听到。到目前为止,感谢所有人。

附注:实际的用例是一个UI数据控件对象,它需要在运行时增加额外的功能。然而,这个问题应该是非常笼统的。

EN

回答 8

Stack Overflow用户

回答已采纳

发布于 2009-06-13 18:47:45

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

它在代码中看起来像like this

代码语言:javascript
复制
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的子类。最后,该函数更改实例ø的类,就像您在代码中所做的那样。

我希望这段代码能展示这种语言功能的合法使用。我想这是我唯一见过在野外使用它的地方。

票数 19
EN

Stack Overflow用户

发布于 2009-06-13 15:31:04

我不确定在这种情况下使用继承是最好的(至少在“重分类”方面是这样)。看起来你是在正确的轨道上,但听起来组合或聚合将是最好的。下面是我正在考虑的一个例子(用未经测试的伪风格代码):

代码语言:javascript
复制
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

如果您不熟悉setsarbitrary argument lists,则可能需要阅读有关它们的更多信息才能获得此示例。

票数 11
EN

Stack Overflow用户

发布于 2009-06-13 18:13:47

这很好。这个成语我已经用过很多次了。不过,要记住的一件事是,这种想法与老式类和各种C扩展不能很好地配合。通常这不是问题,但由于您使用的是外部库,因此您只需确保您不会处理任何旧式的类或C扩展。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/990758

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档