创建对象后更改对象类型(Python 中的类型转换)

Change type of an object after its creation (typecasting in Python)

在我的项目中,我生成了一个类型为 CubicObject 的对象 obj。在运行时,应允许 GUI 设置将 obj 的类型更改为 TofuBox(并返回),具体取决于用户想要做什么以及他(她)们做什么认为该对象最能代表。那么用户应该受益于相应 classes 中实现的特定算法。我正在寻找这种行为的一个很好的实现。我玩过下面的代码,它改变了 __class__ 属性,但我确信这是不好的风格。

class CubicObject(object):
    name = 'Baseclass'

    def __init__(self, sidelength):
        self.sidelength = sidelength


class Tofu(CubicObject):
    name = 'Class A'

    def eat(self):
        print("I've eaten a volume of %s. " % (self.sidelength**3))


class Box(CubicObject):
    name = 'Class B'

    def paint(self):
        print("I painted a surface of %s. " % (self.sidelength**2 * 6))

# user only knows the object is vaguely cubic
obj = CubicObject(sidelength=1.0)
# user thinks the object is a Box
obj.__class__ = Box
obj.paint()
# user changes mind and thinks its a piece of Tofu
obj.__class__ = Tofu
obj.eat()
obj.paint()  # generates an error as it should, since we cannot paint Tofu

我的两个问题是:

潜在的问题是我允许用户在 CubicObject 的子 class 中实现自己的功能,并且应该能够在这些子 class 之间切换,同时该程序是 运行。

What kind properties of class A are transferred to the object 'obj' when I change its class attribute? What functions are called and what attributes are updated, or how else does it happen that obj changes its name to the one of A?

保留所有实例分配的属性 - 也就是说,Python 对象通常具有记录所有实例属性的 __dict__ 属性 - 保留。并且对象的 class 有效地更改为分配的那个。 (Python 运行时禁止 __class__ 为具有不同内存布局的对象赋值)。即:新 class 上的所有方法和 class 属性都可用于实例,并且方法的 none 或先前 class 的 class 属性在那里,就好像这个对象是在这个新的 class 中创建的。 赋值不会触发副作用(如:没有调用特殊方法) 所以 - 对于你正在制作的东西,它 "works".

What other, cleaner ways exist to implement the behaviour I want? If necessary, I could destroy the object obj and recreate another one, but in this case I would like to do so in a generic manner (like obj = RoundObject(subclasstype='Tofu') because of other parts of the code).

是的,正如您所指出的,这不是最好的做事方式。 你可能拥有的是一个 class 层次结构,它具有你需要的不同方法,将你的对象作为一个属性 - 并且根据你正在做的事情,你创建一个新对象 os 这个外部层次结构 - 和保持核心对象的属性不变。这被称为 Adapter Pattern.

class CubicObject(object):
    name = 'Baseclass'

    def __init__(self, sidelength):
        self.sidelength = sidelength


class BaseMethods(object):
    def __init__(self, related):
         self.related = related

class Tofu(BaseMethods):
    name = 'Class A'

    def eat(self):
        print("I've eaten a volume of %s. " % (self.related.sidelength**3))


class Box(BaseMethods):
    name = 'Class B'

    def paint(self):
        print("I painted a surface of %s. " % (self.related.sidelength**2 * 6))

# user only knows the object is vaguely cubic
obj = CubicObject(sidelength=1.0)
# user thinks the object is a Box
box  = Box(obj)
box.paint()

# user changes mind and thinks its a piece of Tofu
tofu = Tofu(obj)

tofu.eat()
# or simply:
Tofu(obj).eat()

您可以自行滚动,手动 classes,或使用实现功能的众所周知且经过测试的库来自动化部分过程。一个这样的库是 zope.interface,它允许您使用适配器模式编写庞大而复杂的系统。因此,您可以拥有数百种不同类型的对象 - 只要它们具有 side_length 属性,您就可以将它们标记为具有接口 "Cubic"。然后你有几十个 classes 用 side_length 属性做 "Cube" 事情 - zope.interface 设施将允许你使用这几十个 [=48] 中的任何一个=] 与任何具有 Cubic 接口的对象一起使用,只需调用所需方法的接口,将原始对象作为参数传递。

但是 zope.interfaces 可能有点难以掌握,因为在将近二十年的使用中根据需要完成的文档很差(并且在某些时候,人们诉诸于使用 XML 文件来声明接口和适配器 - 只需跳过任何处理 XML) 的文档,因此对于较小的项目,您可以像上面那样手动滚动它。

My current implementation already uses a delegate object, but it is impractical because it hides all the interesting functions in the API that I want to provide in that delegate object (I usually duplicate all functions of the delegate object, but that understandably confuses people).

由于您的 real-use 示例很大,因此确实需要学习和使用 zope.interface - 但如果您想允许访问 interface/registry/adapter 系统的另一种解决方法Tofu 上的几个 Cube 方法和其他方法是在 BaseMethods class 上实现的 我有上面的魔法 __getattr__ Python 方法可以让你以透明的方式检索引用对象的方法和属性,不需要 re-write:

class BaseMethods(object):
    def __init__(self, related):
         self.related = related

    def __getattr__(self, attr):
        return getattr(self.related, attr)

borg pattern 的一个变体在这里可能会有帮助:

class CubicObject(object):
    name = 'Baseclass'

    def __init__(self, __shared_state, sidelength):
        self.__dict__ = __shared_state
        self.sidelength = sidelength


class Tofu(CubicObject):
    name = 'Class A'

    def eat(self):
        print("I've eaten a volume of %s. " % (self.sidelength**3))


class Box(CubicObject):
    name = 'Class B'

    def paint(self):
        print("I painted a surface of %s. " % (self.sidelength**2 * 6))

现在,创建共享相同状态的多个实例:

def make_objs(classes, *args, **kwargs):
    __shared_state = {}
    return tuple(cls(__shared_state, *args, **kwargs) for cls in classes)


box, tofu = make_objs(sidelength=1.0, classes=(Box, Tofu))

切换回来并在它们之间强制保持相同状态:

obj = box
obj.paint()
obj = tofu
obj.eat()
obj.paint() 

sidelength 将由双方共享。