如何在方法调用中更换底层对象?

2 投票
5 回答
761 浏览
提问于 2025-04-16 01:01

我原以为在Python中,所有函数和方法的参数都是通过引用传递的,这让我认为下面的代码会有效:

class Insect:
    def status(self):
        print "i am a %s" % self

class Caterpillar(Insect):
        def grow_up(self):
                self = Butterfly() # replace myself with a new object
                return

class Butterfly(Insect):
        pass

my_obj = Caterpillar()
my_obj.status() # i am a <__main__.Caterpillar instance at 0x100494710>

# ok, time to grow up:
my_obj.grow_up()

my_obj.status() # want new Butterfly object, but it is still Caterpillar! :(

那么,我该如何在方法调用中改变对象本身呢?

-- 编辑:

到目前为止,大家的回答有:

1) 改变self.__class__来更改当前对象关联的类。
2) “别那样拿。”

最初的问题是:我该如何在旧类定义的方法中创建一个对象来替代旧对象?

我觉得答案可能是“你不能。”

-- 编辑2:

为什么大家都这么害怕说“这是不可能的”?

5 个回答

1

正如Alex所说,这件事没有严格的方法可以做到。 而且我也想不出有什么方法能让这种做法变得有意义。也许你需要一个工厂函数来代替?或者同一个对象的不同状态?

不过,Python就是这样,你可以创建一个“外壳”对象,它的唯一目的是让你访问一个底层对象。底层对象可以被修改:虽然外壳对象保持不变,但底层对象却可以是另一个。

下面是一个可能的实现方式(我又尝试了一下不太干净的实现方式来处理__getattribute__ - 这个可以做得更好)

# -*- coding: utf-8 -*-
class Insect(object):
        pass

class Catterpillar(Insect):
    a = "I am a caterpillar"
class Butterfly(Insect):
    a = "I am a butterfly"

class Cocoon(object):
    def __init__(self, *args, **kw):
        self._true = Catterpillar(*args, **kw)

    def __getattribute__(self, attr):
        try:
            if attr != "_true":
                return getattr(object.__getattribute__(self, "_true"),attr)
        except AttributeError:
            pass
        return object.__getattribute__(self, attr)
    def __setattr__(self, attr, val):
        if attr != "_true":
            setattr(self._true, attr, val)
        else:
            object.__setattr__(self, attr, val)

    def change(self, *args, **kw):
        self._true = Butterfly(*args, **kw)

这样你就可以得到一些像这样的东西:

>>>
>>> a=  Cocoon()
>>> a.a
'I am a caterpillar'
>>> a.__class__
<class '__main__.Catterpillar'>
>>> a.change()
>>> a.a
'I am a butterfly'
>>> a.__class__
<class '__main__.Butterfly'>
>>>

在这个更改函数中,你可以备份你想要的任何属性,并且可以在__getattribute__方法中排除其他属性不被更改。

2

self.__class__ = Butterfly 可能会有效,因为它是在重新绑定一个有名的名称(这和重新绑定一个无名名称是完全不同的事情)。

重新绑定一个无名名称对之前绑定的对象没有任何影响(除非这个无名名称是唯一指向那个“之前对象”的引用,在这种情况下,那个对象可能会被销毁——但显然在这里不是这种情况,因为 self 是指向和 my_obj 相同对象的引用,所以 self 不能是它的唯一引用)。

编辑

你提到需要初始化重新分类的对象——这很简单:

self.__dict__.clear()
self.__class__ = Butterfly
self.__init__(whatever, you, want)

但在评论中你提到需要“备份属性”——我不太明白你具体指的是什么,或者为什么这会和你直接赋值给 self 的“理想情况”有所不同(这样做当然也会丢掉属性)。也许你的意思是 __init__ 的一些参数需要成为 self 的属性?那么,例如,你可以这样编码:

d, self.__dict__ = self.__dict__, {}
self.__class__ = Butterfly
self.__init__(d['whatever'], d['you'], d['want'])
3

当你说 self = Butterfly() 的时候,你实际上只是改变了变量 self 指向的对象;这并没有改变 self 本身,就像:

x = 1
x = 2

... 并没有把数字 1 变成 2 一样。

通常,当人们需要这样做的时候,他们并不是改变对象的类型(很多编程语言是完全不允许这样做的),而是采用一种组合策略:

class Lepidoptera(Insect):
    def __init__(self):
        self.stage = Caterpillar()

    def status(self):
        print 'I am a %s' % self.stage

    def grow_up(self):
        if self.stage.adult:
            raise TypeError('Already an adult')

        self.stage = Butterfly()

撰写回答