foo(y=y), and similar code that confuses Python newbies

Let’s define a simple Python function:

In [1]: def foo(x):
 ...: return x * x
 ...:

In [2]: foo(5)
Out[2]: 25

In [3]: foo(10)
Out[3]: 100

As we can see, this function has a single parameter, “x”.  In Python, parameters are local variables whose values are set by whoever is calling the function. So we know that x is a local variable in the function “foo” — which means that “x” doesn’t exist outside of the function “foo”.

I can define a more complex function, which has two parameters:

In [4]: def mul(x, y):
 ...: return x * y
 ...:

In [5]: mul(5, 3)
Out[5]: 15

In [6]: mul(6, 8)
Out[6]: 48

In this example, the “mul” function must take two arguments. “x” and “y” are both local variables within “mul”, meaning that they only exist within the “mul” function.

What happens if I define a “y” variable outside of our “mul” function?  That would be a global variable, which shouldn’t be confused with a local one. Local variables exist only within a function, whereas global variables exist outside of functions. For example:

In [7]: y = 100

In [8]: mul(5,3)
Out[8]: 15

I have thus defined the global variable “y”, which has the value 100. Inside of the function, Python ignores our global “y” variable, because according to Python’s scoping (LEGB) scoping rules, local variables get priority.

So far, so good. But now let’s make things a bit more complex: Let’s change our “mul” function such that the “y” parameter takes a default value. In other words, I’ll be able to call “mul” with two arguments (as before) or with one argument (and thus use the default value for “y”):

In [9]: def mul(x, y=10):
 ...: return x * y
 ...:

In [10]: mul(5)
Out[10]: 50

In [11]: mul(7)
Out[11]: 70

In [12]: mul(5,7)
Out[12]: 35

Notice that once again, our global “y” value has no effect whatsoever on our local “y” parameter: Inside of the function, when Python looks for the value of “y”, it finds the local variable by that name, and uses the value accordingly.

We can even go so far as to give both “x” and “y” default values. Here’s how that would look:

In [13]: def mul(x=3, y=10):
 ...: return x * y
 ...:

In [14]: mul()
Out[14]: 30

In [15]: mul(5)
Out[15]: 50

In [16]: mul(7,3)
Out[16]: 21

Let’s say I want to use the default value of x, but pass a value to y.  How can I do that?  By calling the function, but explicitly naming the “y” parameter, along with a value:

In [17]: mul(y=3)
Out[17]: 9

In [18]: mul(y=5)
Out[18]: 15

What happens if I do this:

In [19]: mul(y=y)

In this case, I’m calling the function, and I’m saying that I want to set the “y” local variable to a value.  But what value am I giving it?  Well, I’m not in the function when I call the function.  And thus the only “y” value available to me is the global “y” variable that I had set earlier.

In other words: I want to call the “mul” function, setting the “y” parameter to the current value of the “y” global variable.

Why would we do such a thing? Relative newcomers to Python find this hard to read, and wonder why (or “y”) we use the same variable name on both the left and right sides.  And the answer is… it’s often easier and more convenient.  The example I’ve provided here is contrived, but there are cases in which you might want to define a function called “key” that sorts your list in a particular way.  With that function defined, you can then say

mylist.sort(key=key)

I personally prefer to define my sorting functions using a different convention, starting with the word “by”, so I can say something like

mylist.sort(key=by_last_name)

At the end of the day, this “y=y” code makes sense if you understand Python’s scoping rules, as well as how function defaults are defined and assigned. Want to know more? I’m giving a live, online course about Python functions (including exactly these topics) on Sunday, August 13th. More details are here, and early-bird tickets are still available!

2 thoughts on “foo(y=y), and similar code that confuses Python newbies”

  1. I agree that having different, more specific, names for parameters is often a good idea. One area where I typically DO use the same name is when I am doing text templating, such as using string.Template or a templating engine (ex. jinja2). For example, I often have something like template.substitute(name=name, age=age, height=height). Great post, thanks!

Leave a Reply

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

27 ÷ nine =