Return subclass when calling parent class

I want to get instance of one of subclasses when trying get instance of a superclass (parent class) depending on arguments. For example, I have parent class(I'm using python3):

class Man:

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def say_hello(self):
        print("Hello! My name is {}.".format(self.name))

And subclass:

class YoungMan(Man):

    def say_hello(self):
        print("Hey, man! Wazap?")

If age of Man less 30, I want it become YoungMan:

John = Man('John', 25)
type(John) #<class '__main__.YoungMan'>
John.say_hello() #Hey, man! Wazap?

I tried solve it with Man.__new__():

class Man:

    def __new__(cls, name, age):
        if age < 30:
            return YoungMan(name, age)
        else:
            return super(Man, cls).__new__()
    ...

But John.say_hello() returns Hello! My name is John. So Man methods override YoungMan methods. After I tried use metaclass for Man:

class ManFactory(type):

    def __call__(self, name, age):
        if age < 30:
            return YoungMan(name, age)

class Man(metaclass=ManFactory):
    ...

But it lached on ManFactory.__call__().

I understand that I can use a funtion John = get_Man(name, age) which returns right class, but it isn't so handsome. My question is about how do it like this:

John = Man('John', 25)
type(John) #<class '__main__.YoungMan'>
John.say_hello() #Hey, man! Wazap?

Brad = Man('Brad', 54)
type(Brad) #<class '__main__.Man'>
Brad.say_hello() #Hello! My name is Brad.

Upvotes: 4

Views: 696

Answers (1)

Primusa
Primusa

Reputation: 13498

Not sure if this is good practice but you could set self.__class__:

class Man:

    def __init__(self, name, age):
        if age < 30: self.__class__ = YoungMan
        self.name = name
        self.age = age

    def say_hello(self):
        print("Hello! My name is {}.".format(self.name))


class YoungMan(Man):

    def say_hello(self):
        print("Hey, man! Wazap?")


a = Man("Brad", 15)
print(type(a))
>>><class '__main__.YoungMan'>
a.say_hello()
>>>Hey, man! Wazap?

The main problem with this method is that the YoungMan would still be constructed by Man.__init__() so the classes would have to be compatible. Creating a function get_man() is still the best solution.

Upvotes: 3

Related Questions