|
| 1 | +# pycobytes[33] := What even is a function? |
| 2 | +<!-- #SQUARK live! |
| 3 | +| dest = issues/(issue)/33 |
| 4 | +| title = What even is a function? |
| 5 | +| head = What even is a function? |
| 6 | +| index = 33 |
| 7 | +| tags = technical |
| 8 | +| date = 2025 June 20 |
| 9 | +--> |
| 10 | + |
| 11 | +> *Perfection is achieved not when there is nothing more to add, but when there is nothing more to take away.* |
| 12 | +
|
| 13 | +Hey pips! |
| 14 | + |
| 15 | +Python is, famously, an **object-oriented** programming language. I’ll assume you already know how to create your own `class`es and instantiate objects – they’re usually the endpoint of Python courses, and are better covered by a video tutorial than any written one. |
| 16 | + |
| 17 | +> [!Note] |
| 18 | +> I originally planned an entire sequence of issues on objects, `__dunder__` methods, `__slots__`, the whole rabbit-hole, butttt we’re outta time, so I’d rather not rush or condense those. |
| 19 | +
|
| 20 | +Once you fall into the object-oriented paradigm, it kinda becomes hard to see how a programming language could be anything else, because objects just *make sense*. |
| 21 | + |
| 22 | +Here’s how it works: **Everything is an object.** |
| 23 | + |
| 24 | +Yeah, literally. |
| 25 | + |
| 26 | +Ok, not quite *everything* everything, but linguistic syntax aside, all of programming in Python is just dealing with objects. Defining them, creating them, mutatng them, passing them around, interacting with them, all that. |
| 27 | + |
| 28 | +There’s no good angle of attack for this, so I’ll just throw us straight in the deep end here. Functions? Yeah, those are objects. (In Python, at least.) |
| 29 | + |
| 30 | +```py |
| 31 | +def hallucinate(): # <—- this is an object!! |
| 32 | + print("huh") |
| 33 | +``` |
| 34 | + |
| 35 | +Seems sus? Well, have you never wondered what typing a function without the parentheses does? |
| 36 | + |
| 37 | +```py |
| 38 | +>>> hallucinate # look ma, no brackets |
| 39 | +<function_object> |
| 40 | +``` |
| 41 | + |
| 42 | +👀 |
| 43 | + |
| 44 | +That’s an object right there. Notice we didn’t call the function, cuz no `huh` was printed – you’d need `()` for that. We’re just referencing the function, the function *object*. |
| 45 | + |
| 46 | +Like any object, you can assign functions to variables, pass them into other functions, and store them in containers. |
| 47 | + |
| 48 | +```py |
| 49 | +my_var = hallucinate |
| 50 | + |
| 51 | +my_func(hallucinate) |
| 52 | + |
| 53 | +my_stuff = [hallucinate, hallucinate, hallucinate] |
| 54 | +``` |
| 55 | + |
| 56 | +Really, functions are just a special kind of object that you can do `this_object()` on – i.e. they can be called with `()`. This property just happens to be so fundamental in programming that we give these types of objects an individual name. |
| 57 | + |
| 58 | +> [!Note] |
| 59 | +> If looking at `__dunder__` methods, any object that defines a `__call__()` method is callable... and so could be called a function. But then if you wonder how `__call__()` can be a function, since it would needs its own `__call__()` (it doesn’t, it’ll be implemented in C), it just becomes a rabbit hole down to the lands of Assembly. |
| 60 | +> |
| 61 | +> The ground truth is that some features of the language are atomic in Python, like how (to our current physical understanding) quarks can’t be broken down further. |
| 62 | +
|
| 63 | +One quick detour before we continue. When you learn how to code, you’re introduced to “variables”. I don’t really like this term, because it’s rather misleading. In the example above, what’s `hallucinate`? Would you call it a variable? It certainly looks like one, but I thought it’s also a function? Can you ‘call’ a variable like `this()`? Are functions and variables the same? |
| 64 | + |
| 65 | +I find **identifier** is a much more robust and non-arbitrary term. `hallucinate` is the identifier – i.e. the literal text you type – to refer to the function *object* that it represents. Like here, `sup` is a way of referring to the object `2`: |
| 66 | + |
| 67 | +```py |
| 68 | +sup = 2 |
| 69 | +``` |
| 70 | + |
| 71 | +And if we assign `sup` to another identifier `soup`: |
| 72 | + |
| 73 | +```py |
| 74 | +>>> soup = sup |
| 75 | +``` |
| 76 | + |
| 77 | +Then `soup` is now also an identifier for the same object `2`. |
| 78 | + |
| 79 | +```py |
| 80 | +>>> soup is sup |
| 81 | +True |
| 82 | +``` |
| 83 | + |
| 84 | +Importantly, `soup` does *not* store the ‘variable’ `sup` at all. The two identifiers are unrelated. They just store the same *object*, the number `2`. |
| 85 | + |
| 86 | +So back to functions. When we define a function, the `def` syntax binds it to an identifier: |
| 87 | + |
| 88 | +```py |
| 89 | +def roll(): |
| 90 | + print("yooooooooo") |
| 91 | +``` |
| 92 | + |
| 93 | +And when we call that identifier, we call the function. |
| 94 | + |
| 95 | +```py |
| 96 | +>>> roll() |
| 97 | +yooooooooo |
| 98 | +``` |
| 99 | + |
| 100 | +We can give the function a new identifier by assigning it to... that new identifier! |
| 101 | + |
| 102 | +```py |
| 103 | +>>> roll_faster = roll |
| 104 | +>>> roll_faster() |
| 105 | +yooooooooo |
| 106 | + |
| 107 | +# another one |
| 108 | +>>> roll_harder = roll |
| 109 | +>>> roll_harder() |
| 110 | +yooooooooo |
| 111 | +``` |
| 112 | + |
| 113 | +This doesn’t ‘rename’ the function or anything. All it’s doing is assigning the function object to more than 1 identifier. |
| 114 | + |
| 115 | +```py |
| 116 | +# these now all refer to the same function |
| 117 | +>>> roll() |
| 118 | +yooooooooo |
| 119 | +>>> roll_faster() |
| 120 | +yooooooooo |
| 121 | +>>> roll_harder() |
| 122 | +yooooooooo |
| 123 | +``` |
| 124 | + |
| 125 | +Where might this be relevant? Well, maybe your function isn’t defined already, but is *returned* by another function... |
| 126 | + |
| 127 | +```py |
| 128 | +def mob_spawner(): |
| 129 | + def output_func(): |
| 130 | + print("AHHHHHHHHH") |
| 131 | + |
| 132 | + # returns the function object |
| 133 | + return output_func |
| 134 | +``` |
| 135 | + |
| 136 | +Oh yes, if functions are objects, then there’s nothing stopping functions from returning other functions >:) So now, we can assign the output to an identifier (variable), and then call it: |
| 137 | + |
| 138 | +```py |
| 139 | +>>> spawn = mob_spawner() |
| 140 | +>>> spawn() |
| 141 | +AHHHHHHHHH |
| 142 | +``` |
| 143 | + |
| 144 | +Weird much, eh? And yes, this does mean if you wanted to immediately call the output, you’d do this... |
| 145 | + |
| 146 | +```py |
| 147 | +>>> mob_spawner()() |
| 148 | +AHHHHHHHHH |
| 149 | +``` |
| 150 | + |
| 151 | +Don’t worry, it’ll get even weirder when we look at decorators. Functions returning functions that take in functions to return decorated functions. Don’t we love objects. |
| 152 | + |
| 153 | +Of course, I think it’s still totally fine to refer to variables as, well, variables. This is the expected terminology. However, I think it’s worth appreciating that you can store *any* object in a variable – this includes functions, classes, modules, and more – and in this sense, thinking of them as “identifiers” can help break any misconceptions you might hold. |
| 154 | + |
| 155 | + |
| 156 | +<br> |
| 157 | + |
| 158 | + |
| 159 | +--- |
| 160 | + |
| 161 | +<div align="center"> |
| 162 | + |
| 163 | +[](http://thecodelesscode.com/case/31) |
| 164 | + |
| 165 | +[*The Codeless Code*, Case 31](http://thecodelesscode.com/case/31) |
| 166 | + |
| 167 | +</div> |
0 commit comments