在Python中重分类实例

发布于 2021-01-29 18:31:12

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

现在,我想将此实例转换为我的子类的一个实例,而不更改该实例已经具有的任何属性(但无论如何,我的子类都会覆盖这些属性)。

以下解决方案似乎有效。

# 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个)认为它是一种有效的方法。一个回答者(由ojrac撰写)说,“乍一看,这很奇怪”,我对此表示同意(这是提出问题的原因)。只有一个答案(由Jason Baker撰写;有两个正面的评论和投票)积极地阻止了我这样做,但是这样做的依据更多是示例用例,而不是一般的技术。

  • 不管是肯定的还是不是肯定的,答案都没有找到这种方法的实际技术问题。一个小例外是jls,他提到要提防老式类(这很可能是真的)和C扩展。我想这种方法应该和Python本身一样好(假设后者是正确的),尽管这种方法可以保持答案,但是我想新风格类C扩展应该和Python本身一样好。

关于这是pythonic的问题,有一些肯定的答案,但没有给出真正的原因。看一下Zen(import this),我想在这种情况下最重要的规则是“显式胜于隐式”。不过,我不确定该规则是否支持这种重分类。

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

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

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

我接受了马丁·盖斯勒(Martin
Geisler)的回答,因为Mercurial插件示例是一个非常强大的示例(还因为它回答了我什至没有问过自己的问题)。但是,如果在pythonicity问题上有任何争论,我还是想听听他们的看法。到目前为止谢谢大家。

PS实际用例是UI数据控制对象,需要 在运行时 增加其他功能。但是,这个问题的含义很笼统。

关注者
0
被浏览
53
1 个回答
  • 面试哥
    面试哥 2021-01-29
    为面试而生,有面试问题,就找面试哥。

    当扩展(插件)想要更改表示本地存储库的对象时,在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。在初始化扩展并传递ui(用户界面)和repo(存储库)参数时,Mercurial将调用此方法。

    然后,该函数定义repo碰巧是任何类的子类。这将
    足够简单的子类localrepo,因为扩展需要能够延长对方。因此,如果第一个扩展名更改repo.__class__foo_repo,则下一个扩展名应更改repo.__class__为的子类,foo_repo而不仅仅是的子类localrepo。最后,该函数将更改instanceø的类,就像您在代码中所做的一样。

    我希望这段代码可以显示对该语言功能的合法使用。我认为这是我在野外看到它的唯一地方。



知识点
面圈网VIP题库

面圈网VIP题库全新上线,海量真题题库资源。 90大类考试,超10万份考试真题开放下载啦

去下载看看