Home > Software design >  how do you attach a function to an instance of a class?
how do you attach a function to an instance of a class?

Time:01-01

I used to program in scheme: in scheme (a functional style), functions are values. you can attach a function to a "struct"- the scheme equivalent of a class in python- can you do something similar with python classes?

CodePudding user response:

class MyClass:
   def my_method(self):
       print("Hello world!")

or

By using my_object.my_method = my_function or MyClass.my_method = my_function you can add it dynamically. But this is discouraged, since it makes the code harder to understand.

Edit: To get a bound method, which receives the self arg, you need to add it to the class. This is then also reflected on existing instances. If you add the method to an instance directly, it won't receive self when called.

CodePudding user response:

The answer is yes: functions are first-class values in Python, and you can attach these dynamically to classes or class instances to make methods. In the case of classes this is trivial, but in the case of instances this requires a bit of setup.

To illustrate the difference, consider the following code:

class Hello:
    def say_hello(self):
        print("Hello")

print(type(Hello.say_hello))   # <class 'function'>
print(type(Hello().say_hello)) # <class 'method'>

You can see that for the same method, when it is referenced from the class it is a function type, and when it is referenced from the instance it is a method type. (Also, functions and methods are themselves classes; it's turtles all the way down.)

To dynamically add methods to classes, it it suffices to use regular functions. For example:

def say_hello(self):
    print(f"Hello, {self.name}!")

class Greeter:
    def __init__(self, name):
        self.name = name

Greeter.greet = say_hello
Greeter("Sir Robin").greet() # Prints "Hello, Sir Robin!"

However, methods on instances are bound to the instance; this means that when you call instance.method(), Python supplies the self argument to the method automatically. For normal classes Python handles the binding itself, but this does not happen when you add methods dynamically. For example, this code will result in an error:

def say_hello(self):
    print(f"Hello, {self.name}!")

class Greeter:
    def __init__(self, name):
        self.name = name

greeter = Greeter("Sir Robin")
greeter.greet = say_hello
greeter.greet() # TypeError: say_hello() missing 1 required positional argument: 'self'

Instead you have to bind the method yourself. This can be done using MethodType from the types library.

import types

def say_hello(self):
    print(f"Hello, {self.name}!")

class Greeter:
    def __init__(self, name):
        self.name = name

greeter = Greeter("Sir Robin")
greeter.greet = types.MethodType(say_hello, greeter)
greeter.greet() # Prints "Hello, Sir Robin!"

Caveat: In 99% of cases you will be better off just using regular functions or classes, as dynamically assigning methods is not usually done in Python and will likely confuse other developers. If you really need dynamic lookup, it is much more common to use magic methods like __getattr__ than dynamic method binding. Use this technique at your own risk!

  • Related