# 1.2 The Python Memory Model: Functions and Parameters

## Terminology

Let's use this simple example to review some terminology that should be familiar to you:

```python
# Example 1.

def mess_about(n: int, s: str) -> None:
    message = s * n
    print(message)

if __name__ == '__main__':
    count = 13
    word = 'nonsense'
    mess_about(count, word)
```

In the function declaration,
each variable in the parentheses
is called a **parameter**.
Here, `n` and `s` are parameters of function `mess_about`.
When we call a function, each expression in the parentheses
is called an **argument**.
The arguments in our one call to `mess_about` are `count` and `word`.


## How function calls are tracked

Python must keep track of the function that is currently running,
and any variables defined inside of it.
It stores this information in something called a **stack frame**, or just "frame" for short.

Every time we call a function, the following happens:

1.  A new frame is created and placed on top of any frames that may already exist.
    We call this pile of frames the **call stack**.
2.  Each parameter is defined inside that frame.
3.  The arguments in the function call are evaluated, in order from left to right.
	Each is an expression, and evaluating it yields the id of an object.
    Each of these ids is assigned to the corresponding parameter.

Then the body of the function is executed.

In the body of the function there may be assignment statements.
We know that if the variable on the left-hand-side of the assignment doesn't
already exist, Python will create it.
But with the awareness that there may be a stack of frames, we need a slightly
more detailed rule:

> If the variable on the left-hand-side of the assignment doesn't already exist
> *in the top stack frame*, Python will create it *in that top stack frame*.

For example, if we stop our above sample code right before printing `message`,
this is the state of memory:

```{image} images/Parameters-crop.jpg
:alt: A memory model diagram showing the state of memory before printing `message`.
:width: 450px
:align: center
```

Notice that the top stack frame, for our call to `mess_about`, includes
the new variable `message`.
We say that any new variables defined inside a function are **local variables**;
they are local to a call to that function.

When a function returns,
either due to executing a `return` statement or
getting to the end of the function,
the frame for that function call is deleted.
All the variables defined in it---both parameters and local variables---disappear.
If we try to refer to them after the function has returned, we get an error.
For example, when we are about to execute the final line in this program,

```python
# Example 2. (Same as Example 1, but with a print statement added.)

def mess_about(n: int, s: str) -> None:
    message = s * n
    print(message)

if __name__ == '__main__':
    count = 13
    word = 'nonsense'
    mess_about(count, word)
    print(n)
```

this is the state of memory,

```{image} images/Parameters-popped-crop.jpg
:alt: variables
:width: 450px
:align: center
```

which explains why the final line produces the error
`NameError: name 'n' is not defined`.


## Passing an argument creates an alias

What we often call "parameter passing"
can be thought of as essentially variable assignment.
In the example above, it is as if we wrote
```python
n = count
s = word
```
before the body of the function.

If an argument to a function is a variable,
what we assign to the function's parameter is
the id of the object that the variable references.
This creates an alias.
As you should expect, what the function can do with these aliases depends on whether or not the object is mutable.


## Passing a reference to an immutable object

If we pass a reference to an immutable object,
we can do whatever we want with the parameter and there will be no effect
outside the function.

Here's an example:

```python
# Example 3.

def emphasize(s: str) -> None:
    s = s + s + '!'

if __name__ == '__main__':
    word = 'moo'
    emphasize(word)
    print(word)
```

This code prints plain old `moo`.
The reason is that, although we set up an alias,
we don't (and can't) change the object that both `word` and `s` reference;
we make a new object.
Here's the state of memory right before the function returns:

```{image} images/Passing-immutable-crop.jpg
:alt: variables
:width: 350px
:align: center
```

Once the function is over and the stack frame is gone,
the string object we want (with `moomoo!`) will be inaccessible.
The net effect of this function is nothing at all.
It doesn't change the object that `s` refers to,
it doesn't return anything,
and it has no other effect such as taking user input or printing to the screen.
The one thing it does do, making `s` refer to something new,
doesn't last beyond the function call.

If we want to use this function to change `word`,
the solution is to return the new value and then, in the calling code,
assign that value to `word`:

```python
# Example 4.

def emphasized(s: str) -> str:
    return s + s + '!'

if __name__ == '__main__':
    word = 'moo'
    word = emphasized(word)
    print(word)
```

This code prints out `moomoo!`.
Notice that we changed the function name from
`emphasize` to `emphasized`.
This makes sense when we consider the context of the function call:

```python
    word = emphasized(word)
```

Our function call is not merely performing some action, it is returning a value.
So the expression on the right-hand side has a value: it is the emphasized word.


## Passing a reference to a mutable object

<!--
Wording to possibly use somewhere:

This is really no different than the kinds of side effects we saw earlier
when we learned about aliasing.

Side effects are not a bad thing:
functions are often designed to create side effects.
You just have to know what you're dealing with
(primitive, immutable object, or mutable object)
so you are sure your code will do what you want.
-->

If we wrote code analogous to the broken code in Example 3,
but with a mutable type,
it wouldn't work either.
For example:

```python
# Example 5.

def emphasize(lst: list[str]) -> None:
    lst = lst + ['believe', 'me!']

if __name__ == '__main__':
    sentence = ['winter', 'is', 'coming']
    emphasize(sentence)
    print(sentence)
```

This code prints `['winter', 'is', 'coming']` for the same reason we saw in Example 3.
Changing a reference
(in this case, making `lst` refer to something new)
is not the same as mutating a value
(in this case, mutating the `list` object whose id was passed to the function).
This model of memory illustrates:

```{image} images/Passing-mutable-assignment-crop.jpg
:alt: variables
:width: 600px
:align: center
```

The code below, however, correctly mutates the object:

```python
# Example 6.
def emphasize(lst: list[str]) -> None:
    lst.extend(['believe', 'me!'])

if __name__ == '__main__':
    sentence = ['winter', 'is', 'coming']
    emphasize(sentence)
    print(sentence)
```

This is the state of memory immediately before function `emphasize` returns:

```{image} images/Passing-mutable-crop.jpg
:alt: variables
:width: 450px
:align: center
```

Here are some things to notice:

-   When we begin this program, we are executing the module as a whole.
    We make an initial frame to track its variables, and put the module name in the upper-left corner.
-   When we call `emphasize`, a new frame is added to the call stack.
    In the upper-left corner of the frame, we write the function name.
-   The parameter `lst` exists in the stack frame.
    It comes into being when the function is called.
    And when the function returns, this frame will be discarded, along with everything in it.
    At that point, `lst` no longer exists.
-   When we pass argument `sentence` to `emphasize`,
    we assign it to `lst`.
    In other words, we set `lst` to `id2`, which creates an alias.
-   `id2` is a reference to a `list` object, which is mutable.
    When we use `lst` to access and change that object,
    the object that `sentence` references also changed.
    Of course it does: they are the same object!

## Moral of the story

The situation gets trickier when we have objects that contain references to other objects,
and you'll see examples of this in the work you do this term.
The bottom line is this:
know whether your objects are mutable---at *each* level of their structure.
Memory model diagrams offer a concise visual way to represent that.
