## Functions: definition and simple arguments

Once your program gets larger than a few lines, it’s good practice to modularize the code. This means you should analyze the program structure to identify specific, relatively simple, blocks. Each of these blocks can then be extracted into a separate piece of code which is then called by the main program when needed.

One such modular piece of code is theĀ function. You’ve already used several of Python’s built-in functions such as `print()` for printing output to the console, `len()` for determining the length of lists and so on. Python, like most programming languages, allows you to define your own functions.

## Defining functions

The basic syntax of a function definition is:

```def <function-name>(<arguments (optional)>):
<statements>
<return (optional)>```

`def` is a Python keyword that indicates that a function definition follows. The ‘function-name’ can be any string that begins with a letter (not a number) and contains only letters and numbers. The arguments are a set of parameters that may be passed into the function. Some functions take no arguments at all. The `def` line must end with a colon.

Inside the function defintion, we can insert as many statements as we like, with appropriate indenting. At the end, a function may return a value which can then be used in the calling program, but this is, again, optional.

To illustrate the basics of a function, consider the program:

```def factorial(n):
if not n.isdigit():
return None
n = int(n)
fac = 1 if n == 0 else n
while n > 1:
n -= 1
fac *= n
return fac

while True:
num = input('Enter a positive integer or \'quit\':')
if num == 'quit':
break
print(num + '! =', factorial(num))
```

The function `factorial(n)` calculates the factorial of a non-negative integer n. In the main body of the program (the while loop on line 11) the user is prompted to input an integer. On line 15, this integer is passed to the `factorial()` function. If the object passed as n is not a non-negative integer, the function returns immediately with the Python reserved word `None`. Otherwise, the variable `fac` is initialized to 1 if n is 0 (since 0! = 1) or to n itself otherwise. The factorial is calculated in the while loop on line 6 and returned on line 9.

## How are variables passed to a function?

If you’re used to other languages such as C#, Java or C++, you’ll probably be familiar with the jargon ‘pass by value’ and ‘pass by reference’. The exact meaning of these terms in other languages can get a bit involved and, as we’re studying Python, I won’t go into that here. However, it is useful to understand just what Python is doing when a variable is passed as an argument to a function.

Argument passing is most easily understood if you have a description of Python’s dynamic typing fresh in your mind, so if you need a refresher, please re-read the discussion of dynamic typing.

Referring back to our factorial example above, when the user enters `num`, the variable `num` points to an object containing whatever the user typed in. Let’s say the user entered ’42’, which at this stage is a string, not an int. Then `num` points to an object containing the string ’42’. When `num` is passed (on line 15) as an argument to the factorial function, the variable `n` in the function’s argument list is assigned to point at the same string object as `num`. When `n` is converted to an int on line 4, a new int object containing the value 42 (as an int, not a string) is created and `n` now points to that object. At this point, the link with the original `num` object is broken, so nothing that happens inside the function will affect what `num`, back in the calling program, refers to.

The same argument applies to any immutable data object that is passed to a function. When such an object is first received in the function’s argument list, the argument variable refers to the same object that was passed in. If the argument variable is reassigned at any point within the function, its link with the original object is broken. Thus an immutable object is never changed when it is passed into a function.

This is not, however, true for mutable objects such as lists. Refer again to the post on dynamic typing to see why this is true. Consider this test program:

```def passList(testList):
print('List passed to function:', testList)
if len(testList) > 1:
testList[1] = 'changed'
print('List modified in function:', testList)

startList = [1, 2, 3]
passList(startList)
print('List after function call:', startList)

```

The output is:

```List passed to function: [1, 2, 3]
List modified in function: [1, 'changed', 3]
List after function call: [1, 'changed', 3]```

As you can see, the original `startList` retains the change in its element [1] back in the calling code after the function has run.

Now consider this version:

```def passList(testList):
print('List passed to function:', testList)
testList = [1, 'changed', 3]
print('List modified in function:', testList)

startList = [1, 2, 3]
passList(startList)
print('List after function call:', startList)

```

The output is now:

```List passed to function: [1, 2, 3]
List modified in function: [1, 'changed', 3]
List after function call: [1, 2, 3]```

This time, the reassignment of `testList` on line 3 breaks its connection with the original startList, so any changes to `testList` don’t affect the original list.

## Exercise

Write a program that generates a full deck of 52 standard playing cards. The program should then deal 4 hands of 13 cards each, as in a game of contract bridge or whist. The cards in each hand should be selected randomly from the original deck.

To do this, you should write two functions:

1. Write a function `deck()` that takes no arguments and returns the full deck of 52 cards as a list of tuples, where each tuple should contain one card, giving its suit (Spades, Hearts, Diamonds or Clubs) and its rank (Ace, 2 through 10, Jack, Queen or King). The cards need not be in random order at this stage.
2. Write a function `dealHand(cards, numCards)`. The argument `cards` is a list of tuples that contains the cards that have not yet been dealt. The argument `numCards` is the number of cards to be chosen at random from the cards list. The function should return a list of tuples called `hand`.

Typical output from the program should look something like this:

```Player 1 :
Player 2 :
Player 3 :
[('Diamonds', 10), ('Clubs', 3), ('Clubs', 'Ace'), ('Spades', 7), ('Hearts', 'Ace'), ('Hearts', 8), ('Hearts', 'Queen'), ('Diamonds', 'Queen'), ('Diamonds', 8), ('Hearts', 2), ('Clubs', 'Queen'), ('Hearts', 3), ('Diamonds', 'Jack')]
Player 4 :
[('Spades', 'Jack'), ('Clubs', 10), ('Diamonds', 3), ('Diamonds', 9), ('Hearts', 5), ('Clubs', 6), ('Spades', 'Queen'), ('Hearts', 'Jack'), ('Diamonds', 6), ('Clubs', 7), ('Diamonds', 'King'), ('Hearts', 10), ('Clubs', 9)]```

```from random import *

def deck():
""" Generate a full deck of 52 cards as a list of tuples """
suits = ['Spades', 'Hearts', 'Diamonds', 'Clubs']
rank = ['Ace'] + list(range(2,11)) + ['Jack', 'Queen', 'King']
cards = []
for suit in suits:
for spot in rank:
cards += [tuple((suit, spot))]
return cards

def dealHand(cards, numCards):
""" Select numCards randomly from the list of cards """
hand = []
for i in range(numCards):
choice = randint(0, len(cards) - 1)
hand += [cards[choice]]
cards.remove(cards[choice])
return hand

cardDeck = deck()
hands = []
for player in range(4):
hands += [dealHand(cardDeck, 13)]
print('Player', player + 1, ':\n', hands[player])
```

### deck()

We define two lists on lines 5 and 6 containing the suit names and card ranks. The remainder of the function loops over these lists to create the list of tuples representing the full deck of 52 cards.

### dealHand(cards, numCards)

The randomly chosen cards are to be placed as tuples into the list `hand`, initialized on line 15. We then use the for loop on line 16 to add cards to the hand. We must choose a card at random from the cards remaining in the `cards` list, so we use `randint()` on line 17 to choose a card in that range. The elements of the `cards` list are tuples, so to add a tuple to the `hand` list, we must enclose it in square brackets on line 18 (otherwise the two elements of the tuple would be added as separate entries in the `hand` list).

On line 19, we remove the selected card from the `cards` list, reducing its length by 1. Note that changes made to the `cards` listĀ will be visible after the function runs, for the reasons given above. In this case, this is what we want, since as each hand is dealt, we want the list of undealt cards to be reduced by the number of cards dealt in the last hand.

This function contains no error checking. If you want to make the function foolproof, you need to check that `numCards <= len(cards)`, so that you don’t try to deal more cards than there are cards in the list.

### Main program

On line 22, we initialize `cardDeck` to the full deck. We then deal a sequence of 4 hands by passing `cardDeck` into the `dealHand()` function 4 times. The length of `cardDeck` is reduced by 13 after each hand is dealt.

If you’ve inserted the error check in `dealHand()` above, you should check the return value of `dealHand()` to ensure that you haven’t asked for a hand containing more cards than there are remaining in the deck. The program above will work without these error checks, but if you want to use this code in other card games, the error check will be useful.

This site uses Akismet to reduce spam. Learn how your comment data is processed.