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?
All instance assigned attributes are kept - that is, Python objects normally have a __dict__
attribute where all instance attributes are recorded - that is kept. And the object's class effectively changes to the one assigned. (Python runtime forbids __class__
assignment for objects which have a different memory layout). That is: all methods and class attributes on the new class are available for the instance, and none of the methods or class attributes of the previous class are there, as if the object had been created in this new class.
No side effects are triggered by the assignment (as in: no special method is called)
So - for what you are making, it "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).
Yes, as you've noted, this not is the best way of doing things.
What you could have is a class hierarchy with the distinct methods you need, that get your object as an attribute - and depending on what you are doing, you create a new object os this outer hierarchy - and keep your core object with its attributes unchanged. This is known as the 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()
You can roll it on your own, manual classes, or use a well known and tested library that implements features to automate parts of the process. One such library is zope.interface
, that allows you to write huge and complex systems using the adapter pattern. So you can have hundreds of different types of objects - you can mark them as having the interface "Cubic" as long as they have a side_length
attribute. And then you cna have tens of classes that do "Cube" things with the side_length
attribute - the zope.interface
facilities will allow you to use any of these tens of classes with any of the objects that have the Cubic interface by simply calling the interface for the desired method passing the original object as a parameter.
But zope.interfaces
may be a little hard to grasp, due to poor documentation done as needed over nearly two decades of use (and at some point, people resorted to use XML files to declare interfaces and adapters - just skip any docs that deal with XML), so for smaller projects, you can just roll it manually as above.
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).
Since your real-use example is big, that s a case to indeed learn and use zope.interface
- but another workaround to a fully interface/registry/adapter system, if you want to permit access of several Cube
methods on Tofu
and others is to implement on the BaseMethods
class I have above the magic __getattr__
Python method that would allow you to retrieve methods and attributes on the referred object in a transparent way, with no re-write needed:
class BaseMethods(object):
def __init__(self, related):
self.related = related
def __getattr__(self, attr):
return getattr(self.related, attr)