【问题标题】:Change type of an object after its creation (typecasting in Python)创建对象后更改其类型(Python 中的类型转换)
【发布时间】:2017-06-26 09:30:36
【问题描述】:

在我的项目中,我生成了一个类型为 CubicObject 的对象 obj。在运行时,应允许 GUI 设置将 obj 的类型更改为 TofuBox(并返回),具体取决于用户想要做什么以及他认为对象最能代表什么经过。然后用户应该从相应类中实现的特定算法中受益。我正在寻找这种行为的一个很好的实现。我玩过下面的代码,它改变了__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

我的两个问题是:

  • A 的哪些属性被转移到对象'obj' 当我更改其__class__ 属性时?调用什么函数
    以及更新了哪些属性,或者 obj 是如何发生的 将其名称更改为 A?
  • 还有哪些更简洁的方法可以实现我想要的行为?如果 必要时,我可以销毁对象obj 并重新创建另一个对象, 但在这种情况下,我想以一种通用的方式这样做(比如 obj = RoundObject(subclasstype='Tofu') 因为其他部分 代码)。

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

【问题讨论】:

  • 这看起来像是一个巨大的 XY 问题。你到底想做什么需要这个功能?
  • 因为我很确定我能想出十几个例子来说明为什么这是不可能的。
  • 我认为这是你在 Java 或 C++ 中要做的事情,其中​​有类型转换。
  • 类型转换不会改变对象的类型。它只是将其视为不同的类型。
  • 也许他真正想要的是一个包装类,它可以在运行期间接收不同的内部委托对象。那会更有意义。

标签: python class object dynamic-programming metaclass


【解决方案1】:

类 A 的哪些属性被转移到对象 'obj' 当我更改其 class 属性时?调用了哪些函数和 更新了哪些属性,或者 obj 是如何发生的 将其名称更改为 A 之一?

所有实例分配的属性都被保留——也就是说,Python 对象通常有一个__dict__ 属性,其中记录了所有实例属性——即被保留。并且对象的类有效地更改为分配的类。 (Python 运行时禁止为具有不同内存布局的对象分配__class__)。即:新类上的所有方法和类属性都对实例可用,而前一个类的方法或类属性都不存在,就好像该对象是在这个新类中创建的一样。 分配不会触发任何副作用(如:没有调用特殊方法) 所以 - 对于你正在制作的东西,它“有效”。

还有哪些其他更简洁的方法可以实现我想要的行为?如果 必要时,我可以销毁对象 obj 并重新创建另一个对象, 但在这种情况下,我想以一种通用的方式这样做(比如 obj = RoundObject(subclasstype='Tofu') 因为其他部分的代码)。

是的,正如您所指出的,这不是最好的做事方式。 您可能拥有的是一个具有您需要的不同方法的类层次结构,它将您的对象作为一个属性 - 并且根据您正在做的事情,您可以在这个外部层次结构中创建一个新对象 - 并保持您的核心对象的属性不变.这被称为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()

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

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

我当前的实现已经使用了一个委托对象,但它是 不切实际,因为它隐藏了 API 中所有有趣的功能 我想在该委托对象中提供的(我通常复制 委托对象的所有功能,但可以理解的是 人们)。

由于您的实际使用示例很大,因此确实可以学习和使用zope.interface - 但是如果您想允许访问多个Cube 方法,则可以使用完整接口/注册表/适配器系统的另一个解决方法Tofu 和其他人是在 BaseMethods 类上实现的,我在魔法 __getattr__ Python 方法之上实现,它允许您以透明的方式检索引用对象上的方法和属性,而无需重写:

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

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

【讨论】:

    【解决方案2】:

    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 将由双方共享。

    【讨论】:

    • 只有在没有任何对象改变状态的情况下,这可能只是远程有一些用处。
    • 顺便说一句,由于参数classes的双重声明,您的示例将引发错误
    • 仅使用关键字参数 box, tofu = make_objs(sidelength=1.0, classes=(Box, Tofu)) 或将类放在首位 box, tofu = make_objs((Box, Tofu), 1.0) 时有效。
    猜你喜欢
    • 1970-01-01
    • 2022-08-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-08
    • 1970-01-01
    • 2012-01-16
    • 2018-10-13
    相关资源
    最近更新 更多