在Python中重分类实例

2024-06-26 16:47:46 发布

您现在位置:Python中文网/ 问答频道 /正文

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

现在我想将这个实例转换成我的子类的实例,而不更改该实例已有的任何属性(除了那些我的子类重写的属性)。

下面的解决方案似乎有效。

# 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__感觉不太好。即使这样做行得通,我也忍不住觉得应该有一种更像Python的方式来做这件事。

有吗?


编辑:谢谢大家的回答。以下是我从他们那里得到的:

  • 尽管通过分配给__class__来重新分类实例的想法并不是一个广泛使用的习惯用法,但大多数答案(在编写时,6个答案中有4个)都认为这是一种有效的方法。一位anwswer(由ojrac撰写)说,这“乍一看很奇怪”,对此我表示同意(这是提出这个问题的原因)。只有一个答案(Jason Baker;给出了两个肯定的评论和投票)会让我积极地放弃这样做,但是这样做更多的是基于示例用例,而不是一般的技术。

  • 无论答案是肯定的还是否定的,没有一个在这种方法中发现实际的技术问题。一个小的例外是jls提到要小心旧式类(很可能是真的)和C扩展。我认为新样式的类感知C扩展应该和Python本身一样好(假设后者是真的),但是如果您不同意,请继续回答。

至于这是怎样的Python,有一些积极的答案,但没有给出真正的理由。看看禅(import this),我想在这种情况下最重要的规则是“显式优于隐式”。不过,我不确定这条规则是支持还是反对这样重分类。

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

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

总结:从技术的角度来看,这种方法似乎是可行的;pythonicity问题仍然没有得到回答,而且偏向于“是”

我已经接受了Martin Geisler的答案,因为Mercurial插件的例子是一个非常强大的例子(也因为它回答了一个我甚至还没有问过自己的问题)。不过,如果在python问题上有任何争论,我还是很想听听。谢谢大家。

p.S.实际用例是一个UI数据控制对象,需要在运行时增加额外的功能。不过,这个问题本来是很笼统的。


Tags: andto对象实例答案nameselfis
3条回答

这很好。我用这个成语很多次了。但要记住的一件事是,这种想法在旧式类和各种C扩展中并不适用。通常这不是问题,但是由于您使用的是外部库,因此您只需确保不处理任何旧样式的类或C扩展。

当扩展(插件)想要更改表示本地存储库的对象时,在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 

扩展(我从bookmarks扩展中获取代码)定义了一个模块级函数reposetup。Mercurial将在初始化扩展时调用此函数,并传递一个ui(用户界面)和repo(存储库)参数。

然后,该函数定义一个类repo的子类。仅仅对localrepo进行子类划分是不够的,因为扩展需要能够相互扩展。因此,如果第一个扩展将repo.__class__更改为foo_repo,那么下一个扩展应该将repo.__class__更改为foo_repo的子类,而不仅仅是localrepo的子类。最后,该函数更改实例的类,就像您在代码中所做的那样。

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

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

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以获取此示例,则可能需要阅读更多有关它们的信息。

相关问题 更多 >