0

If I have 2 classes defined like this:

class A(object):
    a = 10

class B(A):
    b = 20

If I create an object:

c = A()

And then do:

c.__class__ = B

Is it a valid way to change ('upgrading') the class of the object, maintaining the primary class attributes and methods and gaining the secondary class attributes and methods?

If true, this only makes sense for this cases where the class to which we are changing the object inherits from the previous class? Best regards.

UPDATED:

To give more context. I have the following class 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']

That i would later like to change/upgrade, to one of the following specific device classes:

class PassiveDevice(EmbrionDevice):
    pass

class ActiveDevice(EmbrionDevice):
    pass

Basically i wanted to ease my copy, avoiding the copy of all the attributes.

LPS
  • 375
  • 3
  • 16
  • 3
    I wouldn't call is a 'valid way'; it is a hack. There may be use-cases for such a hack but you generally want to avoid hacks. – Martijn Pieters Jan 19 '16 at 10:31
  • 2
    You don't "upgrade" a class; it is not a language feature. You can instantiate a class based on state from another class instance, but that's equivalent to just instantiating a class. – msw Jan 19 '16 at 10:46
  • 1
    What would be a valid way of attaining the same result, without having to copy all object attributes of the old class to the new? – LPS Jan 19 '16 at 10:49
  • You should add more context about your use case. Because, whether or not this is technically possible, it's very ugly and hackish and a very bad design approach. – Will Jan 19 '16 at 10:58
  • Since you are doing real code, and not toying with morphing objects, check the zope.interface package and adaptors - they may suit you better. http://docs.zope.org/zope.interface/ (and don't fear the "zope" in the name: zope.interface is a stand-alone package) – jsbueno Jan 20 '16 at 03:41

3 Answers3

2

This is not a valid way to change class of a instance object, A simple example can demonstrate it :-

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>

So now c is instance of class B, Try printing instance attributes:

print c.x
print c.y

It says:

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

Will
  • 24,082
  • 14
  • 97
  • 108
AlokThakur
  • 3,599
  • 1
  • 19
  • 32
  • Why does it allow to `print c.d` and not `print c.x`? – LPS Jan 19 '16 at 11:02
  • Because d is class object's attributes which is initiated when class statement would be executed, instance object of B is not initialized via B.__init__, So x and y is not in memory. I have written a article to explain class object, instance object and their attributes, You may like to refer - http://www.pythonabc.com/python-class-easy-way-understand-python-class/ – AlokThakur Jan 19 '16 at 11:50
  • Instance attributes are set at run time, and by code execution. This **do** change the class of the instance. Instance attributes are not a "magic thing": in Python they have to be set, and you have to run code to set them. Don't having the attributes that are set when a method is run says nothing about an object's class – jsbueno Jan 20 '16 at 03:38
  • Still, if said attributes are defined as properties or by use of other descriptor, changing the class this way will even change the instance attributes (if the property has a default value) – jsbueno Jan 20 '16 at 03:38
  • "Instance attributes are set at run time, and by code execution" Even Class attributes are set at run when code execute class statement. There is a reason behind providing __init__ method and it's not your choice to call __init__ while creating instance. It will be called where you want or not. – AlokThakur Jan 20 '16 at 04:11
  • So If I have a class B with __init__ method, and inside __init__ method attributes self.x and self.y are created. Then User of my class will expect these two attributes to be part of instance of B. – AlokThakur Jan 20 '16 at 04:20
1

That's definitely a hack, and this is also a hack, but I find it do be a bit cleaner:

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

I'm not sure if your approach would work or not, but, this way, you're copying the properties of the old object into a new object that was properly instantiated. If you change .__class__, you can't guarantee that the old variable will reference a properly-created new-class object, as __init__(), __new__(), etc. wouldn't run.

To copy functions, and, this is ugly... but, you could take an approach like this:

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

There are various hacky methods of adding functions to an existing object dynamically. They're all ugly, but, they can be found here.

Will
  • 24,082
  • 14
  • 97
  • 108
  • This will utterly fail for all but the most trivial case. An example is any class with an `__init__()` that does anything other than member assignment. – msw Jan 19 '16 at 10:41
  • I basically pointed that out with my comment about `__init__()`, but how would it fail? The `update()` is called after `__init__()` is called, so it would be the final override. OP wants to retain the properties that would exist in a newly-instantiated `B()` as well. – Will Jan 19 '16 at 10:46
1

You have a misunderstanding of what are "class attributes" in Python - All instance attributes are kept in the instance itself: it does have a __dict__ attribute which is a dictionary where all the attributes defined by code like self.shl = device_info['source_addr_long'] is kept. (This statement creates an shl entry on that dict, for example).

These assignments are run inside the __init__method. If you change an object's class by assigning to its __class__ , it works in a sense: that is its new class. The methods the new class may have defined are now acessible. But all the attributes which were set in the previous class' __init__ still exist, because they are set on the instance's __dict__ that was not changed;. From what I got, this may be exactly what you want - but please note that methods on the original class (as well as class attributes - i.e., attributes defined on the class body itself) will not be acessible, unless the new class itself inherits from the original class. As in the example you show, this is what you are doing, this approach might actually work for you.

But be careful, and do some extensive unit testing, and testing on the interactive console.

An alternative for you might be to use zope.interface - tis will allow you to have a single object, but that "looks like" an object with different attributes and methods to other parts of the code, which might need an specific interface.

jsbueno
  • 99,910
  • 10
  • 151
  • 209