🕵🏽 Investigating python Callables : functions, methods and more...
Tags: python, beginners, programming, computerscience
Published: 2024-04-17 04:41:50

The way python handles functions is a bit unique and the star ⭐ of the show is the "self" argument passed to every function defined within a class, But have you ever wondered, how self is passed to the function? We never pass it during the call. 
How does it get there ?  and how can we replicate it ?

![wondering](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2oleqxx0q2ce6gtm2k8r.gif)


Well in this article, we will try to understand and replicate it. Lets put the `fun in functions`🎉.


## 🧩 Types of functions in Python.
First, we need to understand is the `type` of functions. When using mypy, we annotate them as a Callable but that is not a python standard type. To figure this out lets get coding.
(I know that world is spelt wrong, GET OFF MY BACK)


![function type](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/252g4row3jdipak8qnre.png)

Running the above code gives us the following results.
```
<class 'function'>
```
A bit anticlimactic, so a function is of type `function`, what about methods (foreshadowing...) or functions within a classes ?


![functions in class](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lbv0eyxzdghcykyl57sz.png)

Running the following code gives us a bit more idea. The Output shows us
```
<bound method Foo.foo of <__main__.Foo object at 0x0000020B6B999D00>>
<class 'method'>
<function Foo.foo at 0x0000020B6B9B91C0>
```
We learn 3 things from this.
- The type of a function belonging to a class is `method`
- The reference of a method is a `bound method` tied down to the instance.
- Interestingly, a `method` on a class level is treated as a function.

## 🤨 How does self get there ?
When an instance of a class is created, the functions defined within them get replaced by descriptors, with the instance passed to them as the first argument.
You can learn more about [descriptors from here](https://docs.python.org/3/howto/descriptor.html). The TLDR is, that they are getter and setters for instance attributes.
Try the following code and see what happens 🤩

![Descriptor code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ycc2lnznxuvichti4syo.png)

Checking the type of the function, reveals a funny thing. The type of the bar function has been changed to a `method` and is bound to the int object 10.

![bound method to int](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gy430x38hgp0o10yhxbs.png)

## 🤔 What if you don't want a bound method ?
In the above example, We create a bound method to tie some arguments to the function. What if you don't want to do that ? What if you don't want to use the __get__ method ? How can we recreate the above functionality, Well we use _**decorators**_ !!!!!.


![pseudo bound method using decorators](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6gvjgdpx7iup98p9n12z.png)

If you don't want to use the decorator synthetic sugar, you can use this.

![Higher order functions](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6iqrar0l872j75qd2z58.png)

## 😎 One more thing.
Like we saw above `methods` are defined as a `function` type at the class level. Which means the first argument is unbound and we can do  this.

![Using method as a function](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/24bot0s6ai6u8jk1vwal.png)


## 🎆 Conclusion
Thanks for following this article and hope you learned something new about functions in python. Word of caution, don't use this in production level code, unless you want to annoy your co-workers, if so, go crazy.
Just cause we could to these shenanigans, doesn't mean we  should.

![Jeff-goldblum-meme](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0sjjv9125crfnckkbz6i.gif)