Hi Greg,

On 15/10/19 11:37 AM, Gregory Ewing wrote:
DL Neil wrote:
Is there a technique or pattern for taking a (partially-) populated instance of a class, and re-creating it as an instance of one of its sub-classes?

Often you can assign to the __class__ attribute of an instance
to change its class.

Python 3.7.3 (default, Apr  8 2019, 22:20:19)
[GCC 4.2.1 (Apple Inc. build 5664)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
 >>> class Person:
...  pass
...
 >>> class Male(Person):
...  pass
...
 >>> p = Person()
 >>> p.__class__ = Male
 >>> isinstance(p, Male)
True
 >>>

Brilliantly easy. Thanks!

Is this manipulation documented/explained anywhere? Would you describe it as 'good practice', or even: sensible?


You would then be responsible for initialising any attributes of
Male that Person didn't have.

Understood.

The contents of Person.__init__( -whatever- ) are executed at instantiation.

The contents of Male.__init__( - whatever- ) will be executed during a 'normal' instantiation.

If, however, a Person-instance is 'converted'* into a Male-instance, then Male.__init__() will not be executed.
(haven't bothered to experiment with an explicit call, because...)


In this case, that is not an issue, because apart from the value of "sex", the __init__() actions are all provided by super().

Further experimentation revealed something I wasn't sure to expect. Thus although:

        class Male( Person ):
                etc
        class Person():
                etc

won't work, because Person has yet to be defined; in the correct sequence, we *can* 'anticipate' names within the super-class:

        class Person():
                etc
                def convert( self ):
                        self.__class__ = Male
        class Male( Person ):
                etc

Agreed, better is:

                def convert( self, sub_class ):
                        self.__class__ = sub_class
...
        p = Person()
        p.convert( Male )

Amusingly enough, could "convert"* the p-instance again-and-again.
        
* careful terminology!


PoC code:

class Person():
        
        def __init__( self ):
                self.ID = "Respondent"
                self.converter = { "M":Male, "F":Female }

        
        def questionnaire( self, sex ):
                self.sex = sex
        
        def super_function( self ):
                print( "Pulling on tights and donning cape..." )
        
        def convert( self ):
                self.__class__ = self.converter[ self.sex ]     
        
                
class Male( Person ):
        
        def male_only( self ):
                print( "I am secure in my man-hood" )
        
                
class Female( Person ):
        
        def female_only( self ):
                print( "Thy name is vanity" )


p = Person()

print( "PersonOBJ ~", p )
print( "Person __class__ ~", p.__class__ )
print( "PersonID ~", p.ID )

p.questionnaire( "M" )
print( "M, MaleOBJ ~", p.sex, p.converter[ p.sex ] )
p.convert()

print( "PersonOBJ ~", p )
print( "Person __class__ ~", p.__class__ )
print( "PersonID ~", p.ID )

p.male_only()
p.super_function()

p.questionnaire( "F" )
print( "F, FemaleOBJ ~", p.sex, p.converter[ p.sex ] )
p.convert()

print( "PersonOBJ ~", p )
print( "Person __class__ ~", p.__class__ )
print( "PersonID ~", p.ID )

p.female_only()
p.super_function()

p.male_only()   # Exception


Output:
Showing that:
- the object remains the same, even as its type/class is 'converted'
- the object retains any value established before 'conversion'
or, the 'conversion' process carries-across all data-attributes
- after conversion the sub-class's methods become available
- after conversion the super-class's methods remain available
- after a further 'conversion', the same experience
but methods particular to the first conversion have been 'lost'.
(as expected)

[~]$ python3 person.py
PersonOBJ ~ <__main__.Person object at 0x7f014085bdd0>
Person __class__ ~ <class '__main__.Person'>
PersonID ~ Respondent
M, MaleOBJ ~ M <class '__main__.Male'>
PersonOBJ ~ <__main__.Male object at 0x7f014085bdd0>
Person __class__ ~ <class '__main__.Male'>
PersonID ~ Respondent
I am secure in my man-hood
Pulling on tights and donning cape...
F, FemaleOBJ ~ F <class '__main__.Female'>
PersonOBJ ~ <__main__.Female object at 0x7f014085bdd0>
Person __class__ ~ <class '__main__.Female'>
PersonID ~ Respondent
Thy name is vanity
Pulling on tights and donning cape...
Traceback (most recent call last):
  File "person.py", line 58, in <module>
    p.male_only()       # Exception
AttributeError: 'Female' object has no attribute 'male_only'


--
Regards =dn
--
https://mail.python.org/mailman/listinfo/python-list

Reply via email to