Call me maybe

Python provides us with different ways to call functions. The way you are familiar with is calling with positional arguments, which means that the arguments you give when you call the function are assigned to the parameters in the function definition based on the order they appear. Here's an example:
>>> mysin(30,"deg") # 30 goes to ang and "deg" goes to unit based on order
0.49999999999999994
>>> mysin(1.5,"rad")
0.9974949866040544
>>> mysin(1.5,"deg")
0.026176948307873153
>>> mysin(1.5,"deeg")
Exception: unit must be deg or rad, received: deeg
>>> mysin("deg",45) # fails b/c 45 goes to unit and "deg" goes to ang!      
Exception: unit must be deg or rad, received: 45
So, when calling using positional arguments, you need to get the order of the arguments right! However, we can call the function in a different way if we want. We can call using keyword arguments. What this means is that we give the arguments as name=value pairs when we call the function, and when we call functions this way, the order does not matter!
>>> mysin(ang=30,unit="deg")
0.49999999999999994
>>> mysin(unit="deg",ang=30)
0.49999999999999994
>>> mysin(ang=0.2,unit="rad")
0.19866933079506122
>>> mysin(unit="rad",ang=0.2)
0.19866933079506122
Note: this is a property of how the function is called, not how it is defined.

Playing nice in the Python eco-system: default arguments and doc-strings

It is expected that mysin from the previous section will always be called with an angle. But perhaps we anticipate that the unit would usually be radians. We could make it easier on the user by making "rad" the default value of parameter unit, which means that if the caller neglects to provide a second argument to mysin, the second argument will default to "rad". We do this by putting unit="rad" in the prototype/signature of the function, as shown below.

>>> mysin(0.2)
0.19866933079506122
>>> mysin(0.2,"rad")
0.19866933079506122
>>> mysin(0.2,"deg")
0.003490651415223732
>>> mysin(0.2,unit="deg")
0.003490651415223732
>>> mysin(unit="deg",ang=0.2)
0.003490651415223732
You'll notice another new thing in this function definition: a string delimited by triple-quotes that immediately follows the prototype/signature. This is called a docstring. It documents the use of the function, and users cal look it up by calling: help(funcname). In this case, typing help(mysin) brings up:
mysin(ang, unit='rad')
    mysin(ang,unit) returns sin(ang), where ang is given in unit.
    Supported unit values are "rad" and "deg".  Default is "rad".
If you want to write functions that are easy for others to use, providing docstrings is a good idea!

Mix it up

A common pattern is to define a function with a small number parameters meant to be called as positional arguments, which represent the principal inputs, along with a large number of parameters with default arguments, which represent options to customize how the function works. If the user wants to override a particular default argument, they do that with a keyword argument in the function call.

For example, the function below prints out the elements of a list of points (which are represented as 2-tuples). It provides three additional parameters with default arguments that allow the caller to control how the output is presented.

>>> L = [ (3.2,-2), (5,0.5), (0,2.25) ]
>>> printPoints(L)
(3.2,-2)
(5,0.5)
(0,2.25)
>>> printPoints(L,dir="horizontal")
(3.2,-2) (5,0.5) (0,2.25) 
>>> printPoints(L,dir="horizontal",parens=False,sep=":")
3.2:-2 5:0.5 0:2.25 
>>> printPoints(L,parens=False,sep="\t")
3.2	-2
5	0.5
0	2.25

TODO

We will recreate an IC210 assignment, but do it python style. Write a function ASCIIRectangle that takes a width (number of columns) and a height (number of rows), and prints out a box with that number of rows and columns. There should be optional argument offset that gets a numerical value and indents the box by that many spaces (default 0), optional argument bchar that gets a character and uses that to write the box sides (default is #) and optional argument filled (default is False) that, when True, specifies that the rectangle should be filled in with the bchar character, not merely outlined.
>>> ASCIIRectangle(7,5)
#######
#     #
#     #
#     #
#######
>>> ASCIIRectangle(7,5,offset=5)
     #######
     #     #
     #     #
     #     #
     #######
>>> ASCIIRectangle(7,5,bchar="+")
+++++++
+     +
+     +
+     +
+++++++
>>> ASCIIRectangle(7,5,filled=True)
#######
#######
#######
#######
#######
>>> ASCIIRectangle(7,5,filled=True,bchar='.',offset=7)
       .......
       .......
       .......
       .......
       .......
Hint: Remember that if w is a string and k is an int, w*k creates a string that is k copies of w, all concatenated together.

TODO: Challenge

Add to ASCIIRectangle the option to specify a text message that should appear inside the box. It should (of course) be centered vertically and horizontally. In this case, we want a new parameter msg with a default value that somehow indicates that there is no message. Python has a value None that literally means no value, and we can assign a variable the value None, like x = None. I suggest making the default value of the msg parameter None.

Hint: You can test whether something is None exactly as you would expect: msg == None. Alternatively, you can test whether msg is actually a string using isinstance(msg,str), which returns True if msg is an object of type str, False otherwise.

>>> ASCIIRectangle(7,5,bchar='.',offset=7,msg="rat")
       .......
       .     .
       . rat .
       .     .
       .......
>>> ASCIIRectangle(7,5,bchar='.',offset=7,msg="rat",filled=True)
       .......
       .......
       ..rat..
       .......
       .......

TODO: The Challenge continues

Suppose we would like to add options for the caller to specify top/bottom/center for message vertical alignment, and left/right/center for message horizontal alignment. You can make this available to the user however you want, but a cool way to think about this is to pass a simple string if you want the default positioning, but allow the user to pass a map with key 'm' for the message, key 'v' for vertical positioning (top/center/bottom), and key 'h' for horizontal positioning (top/center/bottom).
>>> ASCIIRectangle(7,5,bchar='.',offset=7,msg={'m':"rat",'v':'top','h':'center'})
       .......
       . rat .
       .     .
       .     .
       .......

Postscript

A lot of python libraries make heavy use of this! For example, Plotly is a module for making nice plots of data. Here is an example from a plotly tutorial. Copy & Paste it into a Python session and see what it does. Notice the use of both keyword and positional arguments!


Christopher W Brown