在 Python 中重分类实例 [英] Reclassing an instance in Python

查看:49
本文介绍了在 Python 中重分类实例的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a class that is provided to me by an external library. I have created a subclass of this class. I also have an instance of the original class.

我现在想把这个实例变成我的子类的一个实例,而不改变该实例已有的任何属性(除了那些我的子类无论如何覆盖的属性).

I now want to turn this instance into an instance of my subclass without changing any properties that the instance already has (except for those that my subclass overrides anyway).

以下解决方案似乎有效.

The following solution seems to work.

# 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 的方式来做到这一点.

However, I'm not convinced that this solution doesn't contain any caveats I haven't thought of (sorry for the triple negation), especially because reassigning the magical __class__ just doesn't feel right. Even if this works, I can't help the feeling there should be a more pythonic way of doing this.

有吗?

感谢大家的回答.这是我从他们那里得到的:

Thanks everyone for your answers. Here is what I get from them:

  • 尽管通过分配给 __class__ 来重新分类实例的想法并不是一个广泛使用的习惯用法,但大多数答案(在撰写本文时 6 个中的 4 个)认为这是一种有效的方法.一个答案(由 ojrac 提供)说它乍一看很奇怪",我同意这一点(这是提出这个问题的原因).只有一个答案(由 Jason Baker 提供;有两个积极的评论和投票)积极阻止我这样做,但是这样做是基于示例用例而不是一般技术.

  • Although the idea of reclassing an instance by assigning to __class__ is not a widely used idiom, most answers (4 out of 6 at the time of writing) consider it a valid approach. One anwswer (by ojrac) says that it's "pretty weird at first glance," with which I agree (it was the reason for asking the question). Only one answer (by Jason Baker; with two positive comments & votes) actively discouraged me from doing this, however doing so based on the example use case moreso than on the technique in general.

没有一个答案,无论是肯定的还是否定的,都不能在此方法中找到实际的技术问题.一个小的例外是 jls,他提到要提防旧式类(这可能是真的)和 C 扩展.我想新风格的类感知 C 扩展应该和 Python 本身一样适用于这种方法(假设后者为真),尽管如果您不同意,请继续提供答案.

None of the answers, whether positive or not, finds an actual technical problem in this method. A small exception is jls who mentions to beware of old-style classes, which is likely true, and C extensions. I suppose that new-style-class-aware C extensions should be as fine with this method as Python itself (presuming the latter is true), although if you disagree, keep the answers coming.

至于 Python 化程度的问题,有一些肯定的答案,但没有给出真正的原因.看看 Zen (import this),我猜在这种情况下最重要的规则是显式优于隐式".不过,我不确定该规则是赞成还是反对以这种方式重新分类.

As to the question of how pythonic this is, there were a few positive answers, but no real reasons given. Looking at the Zen (import this), I guess the most important rule in this case is "Explicit is better than implicit." I'm not sure, though, whether that rule speaks for or against reclassing this way.

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

  • Using {has,get,set}attr seems more explicit, as we are explicitly making our changes to the object instead of using magic.

使用 __class__ = newclass 似乎更明确,因为我们明确地说这现在是类 'newclass' 的对象,期待不同的行为",而不是默默地改变属性,而是让用户对象相信他们正在处理旧类的常规对象.

Using __class__ = newclass seems more explicit because we explicitly say "This is now an object of class 'newclass,' expect a different behaviour" instead of silently changing attributes but leaving users of the object believing they are dealing with a regular object of the old class.

总结:从技术角度来看,这个方法似乎还可以;pythonicity 问题仍然没有答案,偏向于是".

Summing up: From a technical standpoint, the method seems okay; the pythonicity question remains unanswered with a bias towards "yes."

我接受了 Martin Geisler 的回答,因为 Mercurial 插件示例是一个非常强大的示例(还因为它回答了一个我什至还没有问过自己的问题).但是,如果对 pythonicity 问题有任何争论,我仍然想听听他们的意见.到目前为止,谢谢大家.

I have accepted Martin Geisler's answer, because the Mercurial plugin example is a quite strong one (and also because it answered a question I even hadn't asked myself yet). However, if there are any arguments on the pythonicity question, I'd still like to hear them. Thanks all so far.

附言实际用例是一个 UI 数据控件对象,它需要在运行时增加附加功能.但是,这个问题很笼统.

P.S. The actual use case is a UI data control object that needs to grow additional functionality at runtime. However, the question is meant to be very general.

推荐答案

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

Reclassing instances like this is done in Mercurial (a distributed revision control system) when extensions (plugins) want to change the object that represent the local repository. The object is called repo and is initially a localrepo instance. It is passed to each extension in turn and, when needed, extensions will define a new class which is a subclass of repo.__class__ and change the class of repo to this new subclass!

它看起来像这样在代码中:

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(存储库)参数.

The extension (I took the code from the bookmarks extension) defines a module level function called reposetup. Mercurial will call this when initializing the extension and pass a ui (user interface) and repo (repository) argument.

然后该函数定义了任何 repo 类的子类. 简单地将 localrepo 子类化是不够的,因为扩展需要能够相互扩展.因此,如果第一个扩展将 repo.__class__ 更改为 foo_repo,则下一个扩展应将 repo.__class__ 更改为 foo_repo<的子类/code> 而不仅仅是 localrepo 的子类.最后,该函数更改了 instanceø 的类,就像您在代码中所做的一样.

The function then defines a subclass of whatever class repo happens to be. It would not suffice to simply subclass localrepo since extensions need to be able to extend each other. So if the first extension changes repo.__class__ to foo_repo, the next extension should change repo.__class__ to a subclass of foo_repo and not just a subclass of localrepo. Finally the function changes the instanceø's class, just like you did in your code.

我希望此代码可以显示此语言功能的合法使用.我想这是我见过它在野外使用的唯一地方.

I hope this code can show a legitimate use of this language feature. I think it's the only place where I've seen it used in the wild.

这篇关于在 Python 中重分类实例的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆