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:
- 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 argumentcards
is a list of tuples that contains the cards that have not yet been dealt. The argumentnumCards
is the number of cards to be chosen at random from the cards list. The function should return a list of tuples calledhand
.
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)]
See answer
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.