git.net

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Instantiating sub-class from super


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