Search code examples
pythonpython-3.xclasspolymorphismsingle-dispatch

Pythonic(OO) way to choose class method depending on the type of object


Is there any better Pythonic / Object Oriented way to choose which particular class method is to be executed at run time, depending on the type of the object, since using the type() method is not considered elegant(?)

I wrote the following code for the three data types I was working with. It basically stores different functions in dictionary as values pairing it with its corresponding object type as keys:

import pathlib
import _io

########################################
class choose_for :

    def __init__(self, var):
        func_dict = {str:self.func1, pathlib.WindowsPath:self.func2, _io.TextIOWrapper:self.func3}
        self.func = func_dict[type(var)]

    def func1(self) :
        print("Because it is for STRING like \n")

    def func2(self) :
        print("Because it is for PATH like \n")

    def func3(self) :
        print("Because it is for FILE_POINTER like \n")

    def execute(self):
        print("This object will be executed by : ", self.func)
        self.func()

########################################
var1 = str("something")

var2 = pathlib.Path()

var3 = _io.open("file.txt")

########################################
chosen1 = choose_for(var1)
chosen1.execute()

chosen2 = choose_for(var2)
chosen2.execute()

chosen3 = choose_for(var3)
chosen3.execute()

########################################
var3.close()

It gives the following output :

This object will be executed by :  <bound method choose_for.func1 of <__main__.choose_for object at 0x0000020A150A3088>>
Because it is for STRING like

This object will be executed by :  <bound method choose_for.func2 of <__main__.choose_for object at 0x0000020A1509EFC8>>
Because it is for PATH like

This object will be executed by :  <bound method choose_for.func3 of <__main__.choose_for object at 0x0000020A15276B48>>
Because it is for FILE_POINTER like

Also is there any technical term, for what I am trying to do here? I think it might be related to single-dispatch, function-overloading or polymorphism, but I am not sure.


Solution

  • This is, indeed, single-dispatch, which Python supports via the functools module. No class is needed.

    from functools import singledispatch
    
    
    @singledispatch
    def execute(arg):
        print(f"something with type {type(arg)}")
    
    @execute.register(str):
    def _(arg):
        print("Because it is for STRING like \n")
    
    @execute.register(pathlib.Path):
    def _(arg):
        print("Because it is for PATH like \n")
    
    @execute.register(io.FileIO)
    def _(arg):
        print("Because it is for FILE_POINTER like \n")
    

    Testing it, you'll see

    >>> execute("something")
    Because it is for STRING like
    >>> execute(pathlib.Path())
    Because it is for PATH like
    >>> execute(_io.open("file.txt"))
    Because it is for FILE_POINTER like
    >>> execute(3)
    something with type <class 'int'>
    

    To use single-dispatch with a method, use singledispatchmethod, which will dispatch on the second argument of the method, skipping the implicitly provided instance or class argument.