【问题标题】:Changing an object's class while maintaining its attributes and functions在保持其属性和功能的同时更改对象的类
【发布时间】:2016-01-19 10:29:34
【问题描述】:

如果我有 2 个这样定义的类:

class A(object):
    a = 10

class B(A):
    b = 20

如果我创建一个对象:

c = A()

然后做:

c.__class__ = B

改变(“升级”)对象的类,保持主要的类属性和方法,获得次要的类属性和方法,是一种有效的方式吗?

如果为真,这仅在我们将对象更改为的类继承自前一个类的情况下才有意义?最好的问候。

更新:

提供更多上下文。 我有以下类 EmbrionDevice。

class EmbrionDevice(object):
    def __init__(self, device_info, *args, **kwargs):
        super(EmbrionDevice, self).__init__(*args, **kwargs)
        # Serial number unique 64-bit address factory-set
        self.shl = device_info['source_addr_long']
        # 16-bit network address
        self.my = device_info['source_addr']
        # Node identifier
        self.ni = device_info['node_identifier']
        # Parent Address
        self.pa = device_info['parent_address']
        # Device type, 0-coordinator, 1-router, 2-End Device
        self.dt = device_info['device_type']
        # Device type identifier xbee or Digi device
        self.dd = device_info['device_type_identifier']
        # Device attributes summary in a dictionary
        self.info = device_info
        # Embrion future function
        self.function_identifier = None
        # Device state definition
        self.state = DEV_STATE_CODES['embrion']
        self.status = DEV_STATUS_CODES['no status']

我稍后想更改/升级到以下特定设备类之一:

class PassiveDevice(EmbrionDevice):
    pass

class ActiveDevice(EmbrionDevice):
    pass

基本上我想简化我的副本,避免复制所有属性。

【问题讨论】:

  • 我不会打电话是一种“有效的方式”;这是一个黑客。这种黑客攻击可能有用例,但您通常希望避免黑客攻击。
  • 你没有“升级”一个类;它不是语言功能。您可以根据来自另一个类实例的状态实例化一个类,但这相当于只实例化一个类。
  • 什么是获得相同结果的有效方法,而不必将旧类的所有对象属性复制到新类?
  • 您应该添加更多关于您的用例的上下文。因为,无论这在技术上是否可行,它都是非常丑陋和骇人听闻的,而且是一种非常糟糕的设计方法。
  • 由于您正在编写真正的代码,而不是玩弄变形对象,请检查 zope.interface 包和适配器 - 它们可能更适合您。 docs.zope.org/zope.interface(不要害怕名称中的“zope”:zope.interface 是一个独立的包)

标签: python python-2.7 python-internals


【解决方案1】:

这不是改变实例对象类的有效方法,一个简单的例子可以证明它:-

class A(object):
    a = 10
    def __init__(self):
        self.b = 20
        self.c = 30

class B(A):
    d = 35
    def __init__(self):
        self.x = 70
        self.y = 80

c = A()
c.__class__ = B

print c

<__main__.B object at 0x02643F10>

所以现在c 是 B 类的实例,尝试打印实例属性:

print c.x
print c.y

上面写着:

AttributeError: 'B' object has no attribute 'x'

【讨论】:

  • 为什么允许print c.d 而不允许print c.x
  • 因为 d 是类对象的属性,在执行类语句时会初始化,所以 B 的实例对象不是通过 B.__init__ 初始化的,所以 x 和 y 不在内存中。我写过一篇文章讲解类对象、实例对象及其属性,大家可以参考——pythonabc.com/python-class-easy-way-understand-python-class
  • 实例属性在运行时设置,并通过代码执行。这改变了实例的类。实例属性不是“神奇的东西”:在 Python 中必须设置它们,并且您必须运行代码来设置它们。没有在运行方法时设置的属性说明对象的类
  • 仍然,如果所述属性被定义为属性或使用其他描述符,以这种方式更改类甚至会更改实例属性(如果属性具有默认值)
  • "实例属性在运行时设置,并通过代码执行" 甚至类属性在代码执行类语句时在运行时设置。提供 init 方法是有原因的,在创建实例时调用 init 不是您的选择。它会在你想要或不想要的地方调用。
【解决方案2】:

这绝对是一个 hack,这也是一个 hack,但我发现它确实有点干净:

In [1]: class A(object):
   ...:     a = 10
   ...:

In [2]: class B(A):
   ...:     b = 20
   ...:

In [3]: c = A()

In [4]: new_c = B()

In [5]: new_c.__dict__.update(c.__dict__.copy())

In [7]: repr(new_c)
Out[7]: '<__main__.B object at 0x102f32050>'

In [8]: new_c.b
Out[8]: 20

我不确定您的方法是否可行,但是,通过这种方式,您将旧对象的属性复制到正确实例化的新对象中。如果更改.__class__,则无法保证旧变量将引用正确创建的新类对象,因为__init__()__new__() 等将无法运行。

要复制函数,而且,这很丑……但是,您可以采取这样的方法:

In [18]: for name, obj in c.__class__.__dict__.iteritems():
   ....:     if hasattr(obj, '__call__'):
   ....:         # Copy the function.
   ....:
test

有多种 hacky 方法可以动态地向现有对象添加函数。他们都很丑,但是,他们可以找到here

【讨论】:

  • 除了最微不足道的情况外,这将完全失败。一个示例是任何具有__init__() 的类,它执行除成员分配之外的任何操作。
  • 我基本上在我对__init__() 的评论中指出了这一点,但它怎么会失败呢? update() 是在 __init__() 被调用之后被调用的,所以这将是最终的覆盖。 OP 也希望保留新实例化的 B() 中存在的属性。
【解决方案3】:

你对 Python 中的“类属性”有什么误解—— 所有实例属性都保存在实例本身中:它确实有一个 __dict__ 属性,这是一个字典,其中保存了由代码定义的所有属性,例如 self.shl = device_info['source_addr_long']。 (例如,此语句会在该字典上创建一个 shl 条目)。

这些分配在__init__方法中运行。如果您通过分配给它的 __class__ 来更改对象的类,它在某种意义上是有效的:那是它的新类。新类可能已经定义的方法现在可以访问了。但是在上一个类中设置的所有属性__init__ 仍然存在,因为它们设置在实例的__dict__ 上并没有改变;。据我所知,这可能正是您想要的 - 但请注意原始类上的方法(以及类属性 - 即在类主体本身上定义的属性)将无法访问,除非 em> 新类本身继承自原类。正如您展示的示例所示,这就是您正在做的事情,这种方法实际上可能对您有用。

但要小心,做一些广泛的单元测试,并在交互式控制台上进行测试。

您的另一种选择可能是使用zope.interface - tis 将允许您拥有一个对象,但“看起来”与代码的其他部分具有不同属性和方法的对象,这可能需要特定的界面。

【讨论】:

    猜你喜欢
    • 2013-05-31
    • 1970-01-01
    • 1970-01-01
    • 2023-01-13
    • 1970-01-01
    • 2018-03-01
    • 2011-02-13
    • 1970-01-01
    • 2022-12-14
    相关资源
    最近更新 更多