Equivalent of C# delegates in Python

Coming from C# background, I was trying to understand whether Python allows to pass obj.method where ordinary function is expected, and if yes, how could it possibly work.

Consider this code fragment:

def call_twice(f):
    f()
    f()

class Foo:
    def __init__(self, data):
        self.data = data
    
    def print_value(self):
        print(f"Value is {self.data}")

foo = Foo(42)
call_twice(foo.print_value)  # can it work? how?

The short answer is: yes, it works, foo.print_value returns an equivalent of C# delegate that knows about foo variable.

Longer answer

Unlike C++ and the languages influenced by it, methods definitions in Python accept “this” parameter explicitly. It is usually called “self” by convention, but in fact any name would work:

class Foo:
  def print_value(this):
     print(f"Value is {this.data}")

It looks suspiciously like C, and in C passing a one-argument function to something that expects a zero-argument function would not work. So, how can we pass foo.print_value to call_twice() and then invoke it with no arguments?

The answer is that while Foo.print_value is indeed an ordinary function that accepts one argument, foo.print_value is a callable object that accepts no arguments. When invoked, it calls Foo.print_value(foo). This is very similar to how C# delegates work, and somewhat similar to C++ functional objects that define operator ().

In other words, foo.print_value() is not simply syntactic sugar for Foo.print_value(foo) as I originally thought. The expression foo.print_value returns an object of type method. This object can then be called immediately, or passed to another function to be called later.

More details here: https://stackoverflow.com/questions/6685232/how-are-methods-classmethod-and-staticmethod-implemented-in-python

Some Interesting Implications

Since methods are actually regular functions, they can be applied to arbitrary objects. Consider this code:

class Bar:
    def run(self):
        self.go("fast")

    def go(self, msg):
        print(f"Bar.go({msg})")

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

    def go(self, msg):
        print(f"{self.name} is going {msg}!")

bus = Bus("Yellow bus")
Bar.run(bus)  # prints "Yellow bus is going fast!"

We apply Bar.run() to an object of type Bus, which is completely unrelated to Bar, and yet it invokes Bus.go() and prints the expected message. Of course, this feature should be used with care, but it does occasionally become useful, e.g. when we want to be 100% certain method of what class we are calling on an object in presence of multiple inheritance.

2 Comments


    1. This is only partially true. In C# a delegate is a pointer to function, plus optional target object. A delegate may point to a lambda, or to a regular function/method. So, delegates and Python method and function objects are quite similar. In C++ the equivalent of delegate would be a function pointer or a struct containing a pointer to member, a pointer to a class object, and operator ().

      Reply

Leave a Reply to Sergey Babkin Cancel reply

Your email address will not be published. Required fields are marked *