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.
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))
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
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 = '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  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.
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:
- 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.
- Write a function
dealHand(cards, numCards). The argument
cardsis a list of tuples that contains the cards that have not yet been dealt. The argument
numCardsis the number of cards to be chosen at random from the cards list. The function should return a list of tuples called
Typical output from the program should look something like this:
Player 1 : [('Spades', 3), ('Clubs', 5), ('Hearts', 9), ('Spades', 8), ('Diamonds', 'Ace'), ('Hearts', 6), ('Spades', 5), ('Clubs', 'King'), ('Diamonds', 5), ('Spades', 2), ('Hearts', 'King'), ('Diamonds', 4), ('Spades', 'King')] Player 2 : [('Hearts', 7), ('Clubs', 'Jack'), ('Spades', 9), ('Clubs', 8), ('Diamonds', 7), ('Diamonds', 2), ('Spades', 6), ('Hearts', 4), ('Spades', 10), ('Spades', 4), ('Spades', 'Ace'), ('Clubs', 2), ('Clubs', 4)] 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])
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.
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
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.
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.