Home > other >  Assigning a method to an object as a variable
Assigning a method to an object as a variable

Time:10-15

I wanted objects to call a certain method (the specific one being assigned in a variable) in their routine. I managed to do it with a dict and I also managed to do it by assigning the variable after object creation. Which way is better? Is there another cleaner way?

First way that works (dict):

class Foo:
    def __init__(self, name, funcin) -> None:
        self.name = name
        self.func = {
            'bear': self.bear,
            'boar': self.boar,
        }[funcin]
    
    def routine(self):
        print('fluff')
        self.func()

    def bear(self):
        print(f'I, {self.name}, am a bear and I like honey.')
    
    def boar(self):
        print(f'I, {self.name}, am a boar and I like fruits.')

bob = Foo('bob', 'bear')
mark = Foo('mark', 'boar')

bob.routine()
mark.routine()

Second way that worked (another line to assign):

class Foo:
    def __init__(self, name) -> None:
        self.name = name
    
    def routine(self):
        print('fluff')
        self.func()

    def bear(self):
        print(f'I, {self.name}, am a bear and I like honey.')
    
    def boar(self):
        print(f'I, {self.name}, am a boar and I like fruits.')
    
Kona = Foo('Kona')
Kona.func = Kona.bear
John = Foo('John')
John.func = John.boar

Kona.routine()
John.routine()

Ways that didn't work:

# NameError: name 'bear' is not defined
class Foo:
    def __init__(self, name, funcin) -> None:
        self.name = name
        self.func = self.funcin
    def bear(self):
        print(f'I, {self.name}, am a bear and I like honey.')
bob = Foo('bob', bear)
bob.func()

# NameError: name 'self' is not defined
class Foo:
    def __init__(self, name, funcin) -> None:
        self.name = name
        self.func = funcin
    def bear(self):
        print(f'I, {self.name}, am a bear and I like honey.')
bob = Foo('bob', self.bear)
bob.func()

# NameError: name 'Kim' is not defined
class Foo:
    def __init__(self, name, funcin) -> None:
        self.name = name
        self.func = funcin
    def bear(self):
        print(f'I, {self.name}, am a bear and I like honey.')
Kim = Foo('Kim', Kim.bear)
Kim.func()   

# TypeError: Foo.bear() missing 1 required positional argument: 'self'
class Foo:
    def __init__(self, name, funcin) -> None:
        self.name = name
        self.func = funcin
    def bear(self):
        print(f'I, {self.name}, am a bear and I like honey.')
bob = Foo('bob', Foo.bear)
bob.func()

CodePudding user response:

The class's methods are available just as they are in a dict via the class's __dict__ attribute. So in the first solution you presented, you could also define self.func like this:

self.func = Foo.__dict__[funcin].__get__(self)

So with __dict__[funcin] we retrieve the method from the class object, and with __get__ we we get a function that has the correct self binding.

To make it even more generic so that you don't have to mention Foo:

self.func = self.__class__.__dict__[funcin].__get__(self)

CodePudding user response:

A common solution is class inheritance. There are multiple options from abstract base classes to mixins, but here is a simple start

class Foo:
    def __init__(self, name) -> None:
        self.name = name
        
    def func(self):
        raise NotImplementedError
    
    def routine(self):
        print('fluff')
        self.func()

class Bear(Foo):
    
    def func(self):
        print(f'I, {self.name}, am a bear and I like honey.')
    
class Boar(Foo):
    
    def func(self):
        print(f'I, {self.name}, am a boar and I like fruits.')

bob = Bear('bob')
mark = Boar('mark')

bob.routine()
mark.routine()

CodePudding user response:

You could use getattr():

class Foo:
    def __init__(self, name, funcin) -> None:
        self.name = name
        self.func = getattr(self,funcin)
    
    def routine(self):
        print('fluff')
        self.func()

    def bear(self):
        print(f'I, {self.name}, am a bear and I like honey.')
    
    def boar(self):
        print(f'I, {self.name}, am a boar and I like fruits.')

If you don't need to perform additional (common) fluff, you could simply assign self.routine = getattr(self,funcin) and not even define the routine() function.

  • Related