Home      |       Contents       |       About

Prev: Functions defined       |       Next: Variable Scope

Arguments

Arguments pass by 'object reference'

  • In Python arguments always pass to functions by object reference. Let's explore this issue a bit more and see what happanes after we start changing the arguments in the function
In [2]:
def myfunc(a, b):
    a = 31
    b[0] = 'K'
    return a, b

x = 10
y = [1,2,3]
newx, newy = myfunc(x, y)
print(newx, newy)
print(x, y)
31 ['K', 2, 3]
10 ['K', 2, 3]
  • For some strange reason we need a function that takes an integer and a list as arguments and changes the integer to 31 and the first item of the list to 'K'
  • When running the program we see that x and a have different values (as expected) but lists y and b are the same (did you expect it?)
  • Let's see what happens with the help of the visualization below

  • Left panel: when calling the function we see that arguments are passed by object reference (arguments are referenced both by parameter names in the main program and variable names in the function)
  • Right panel: when assigning new values in the function, a new obect (int 31) has been constructed and bound to name 'a' BUT in the list object the first item changed its value 'in place' (now new copy constructed). So, the names 'b' in the function and the name 'y' in the main program continue the shared object reference.

  • You can further explore the visualization here

This leads us to an importan rule of Python function operation:

Arguments pass by object reference but if they are assigned new values in the function, then:

  • for immutable types (like integer, strings. etc.) new objects are constructed locally in the function
  • for mutable types (lists, dicts, etc.) item assignments happen 'in place' and so any changes in the function are reflected in the main program as well.
  • But what if I want changes in the function not to be reflected in the main program even for mutable obects?
  • A simple answer would be to construct a copy of the mutable object locally in the function
In [3]:
def myfunc(a, b):
    a = 31
    b = b[:]
    b[0] = 'K'
    return a, b

x = 10
y = [1,2,3]
newx, newy = myfunc(x, y)
print(newx, newy)
print(x, y)
31 ['K', 2, 3]
10 [1, 2, 3]
  • List y now remains intact

Flexible argument passing

  • Python has a number of ways to facilitate flexible argument passing, a thing that greatly facilitates writing compact and efficient code. Let's see some of these.

(a) Default parameter values

  • Functions may have default parameter values written in the form 'parameter = expression'. When calling the function the corresponding argument may either:
    • ...be omitted; in this case the parameter's default value is used, or
    • ...explicitly be assigned a different value (from the default)
In [11]:
def power(x,y=2):
    return x**y

a = power(10)    # y takes the default value 2
b = power(10,3)  # y is explicitly assigned the value 3 
a, b
Out[11]:
(100, 1000)
  • When defining a function all non-default parameters must precede parameters with default values; else an exception is raised
In [16]:
def power1(x,y=2):      # non-default parameter x preceds y
    return x**y

def power2(y=2,x):      # non-default parameter x is after y
    return x**y
  File "<ipython-input-16-44b51dd8fdeb>", line 4
    def power2(y=2,x):      # positional parameter x is after y
              ^
SyntaxError: non-default argument follows default argument

(b) Keyword Arguments

  • Similar to the default/non-default parameters arguments can be of two kinds:
    • keyword argument: any argument preceded by a name (like an assignment)
      power(5, y=3)   # 3 is a keyword argument
    • positional argument: not a keyword argument; must always be positioned before keyword arguments
      power(5, y=3)   # 5 is a positional argument    
  • Keyword-only arguments: a sequence of keyword-only arguments can be defined by preceding their names using a sole asterisk '*' symbol (only once). Using this syntax we have to explicitly pass arguments by keyword when calling the function. This adds readability to the code as it is easily understood what the arguments stand for.
In [1]:
def kinetic(txt, *, m, v):
    '''
    Returns the kinetic energy of a body
    with mass m moving with speed v
    '''
    en = (m * v**2)/2
    print(txt, en)
    return en 


#...................
kinetic('Kinetic...', m=10, v=20)
#...................
Kinetic... 2000.0
Out[1]:
2000.0
  • varied-positional and varied-keyword arguments: when we don't really know how many arguments we will eventually pass to a function and what they will represent, we can write the function headline like this:
                  func_name(*args, **kwargs):
                      ..........................
  • Here, *args mean literally "as many positional arguments as you wish". Similarly **kwargs means "as many keyword arguments as you wish"
    • Note that the important symbol is the asterisk(s) '*' and '**' but NOT the 'args' or 'kwargs' names. These are simply conventions to remind us of 'arguments' and 'keyword arguments' respectively. In fact, we could use other names as well (like *vars, or **keywords, etc..)
  • When calling a function like this, then:
    • varied-positional arguments are packed inside the function in a tuple object
    • varied-keyword arguments are packed inside the function in a dictionary object
  • Examples
    Varied keyword and positional arguments are a great tool when we need to write a multi-purpose function able to accept an undefined number of arguments.
  • Suppose we need a function that computes the total population of an initially unknown number of cities. We can write a function that accepts an undefined number of positional arguments; in this case integers representing cities population. The function then simply adds the input data and returns the sum.
  • In the example below:
    • The undefined number of arguments is declared as *args.
    • Within the cities_pop() function arguments are handled as a tuple; see, for example, the for loop that computes the total population. In the example we call cities_pop() twice and we pass a different number of arguments each time.
In [3]:
def cities_pop(*args):
    '''
    Accepts a varied number of data
    integers representing cities population 
    '''
    total=0
    print('Cities: ', len(args), type(args))
    for element in args:
        total += element
    return total


case1 = cities_pop(65000, 72000)
print('Total population:',case1,'\n')

case2 = cities_pop(230000, 72000, 1000000, 37000)
print('Total population:',case2,'\n')
Cities:  2 <class 'tuple'>
Total population: 137000 

Cities:  4 <class 'tuple'>
Total population: 1339000 

  • Suppose now we want to pass keyword arguments to the function. We can do that by using the **kwargs notation. In the example below:
    • The undefined number of arguments is declared as **kwargs.
    • Within the cities_pop() function arguments are handled as a dict (dictionary); see, for example, the for loop that computes the total population but it also prints out the keyword city name.
    • In the example we call cities_pop() twice and we pass a different number of arguments each time. In this case, however, we must write the arguments in the form of 'key=value' pairs as demonstrated in the example.
In [4]:
def cities_pop(**kwargs):
    '''
    Accepts a varied number of data
    integers representing cities population,
    along with key-words: city names
    '''
    
    total=0
    print('Cities: ', len(kwargs), type(kwargs))
    for pair in kwargs.items():
        print('City: ',pair[0], 'Population: ', pair[1])
        total += pair[1]
    return total


case1 = cities_pop(Thessaloniki=810000,
                   Athens=3200000)
print('Total population', case1,'\n')

case2 = cities_pop(Thessaloniki=810000,
                   Athens=3200000,
                   Ioannina=86000,
                   Patras = 196000)
print('Total population:',case2,'\n')
Cities:  2 <class 'dict'>
City:  Thessaloniki Population:  810000
City:  Athens Population:  3200000
Total population 4010000 

Cities:  4 <class 'dict'>
City:  Thessaloniki Population:  810000
City:  Patras Population:  196000
City:  Athens Population:  3200000
City:  Ioannina Population:  86000
Total population: 4292000 

Unpacking return objects in .format()

  • When you want to directly print the objects that a function returns, you can unpack the returned tuple using again the ' * ' notation in the .format() method, as you see below
In [30]:
import random, math

def stat(numlist):
    '''
    Input: list of numerics
    Output: Mean and Standard deviation
    '''
    mn = sum(numlist)/len(numlist)
    sm = 0
    for k in numlist:
        sm += pow((k-mn), 2)
    sd = math.sqrt(sm/(len(numlist)-1))
    return mn, sd

help(stat)
mylist = [1,2,3,4,5] #[random.randint(1,100) for i in range(100)]
mean, std = stat(mylist)
print('Mean: {:5.2f}, Standard deviation: {:5.2f}'.format(*stat(mylist)))
Help on function stat in module __main__:

stat(numlist)
    Input: list of numerics
    Output: Mean and Standard deviation

Mean:  3.00, Standard deviation:  1.58

. Free learning material
. See full copyright and disclaimer notice