, ,

Turn Repeated Code Into Functions (Python for Beginners, Part 9)

Stop copying the same lines. Write Python functions with def, pass arguments and defaults, return values, avoid the mutable default trap, then wrap the expense tracker in add and show functions.

Python for Beginners · Part 9 of 20

Here is a snippet that turns up in a lot of first programs. Two greeting lines, typed out again for every new person.

repeat.py
name = 'Asha'
print('Hello, ' + name)
print('Welcome aboard.')
name = 'Ravi'
print('Hello, ' + name)
print('Welcome aboard.')
output
Hello, Asha
Welcome aboard.
Hello, Ravi
Welcome aboard.

Change the wording once and you have to change it in every copy, and you will miss one. A function lets you write those two lines a single time, give them a name, and run them by that name whenever you like.

greet.py
def greet(name):
    print('Hello, ' + name)
    print('Welcome aboard.')

greet('Asha')
greet('Ravi')
output
Hello, Asha
Welcome aboard.
Hello, Ravi
Welcome aboard.
The short versionA function is a named block of code. You write it once after def, then run it by name as often as you want. Values you hand in are called arguments, and a function can hand one value back to you with return. Fewer copies of the same code means fewer bugs and a single place to fix things.

This part follows Part 8, where each expense became a small dictionary. You need Python 3 and a text editor, nothing to install, and about fifteen minutes. Every block is meant to be copied and run as is, and the output under each one is what you should see on your own screen. Errors along the way are normal, not a sign you are doing badly. When one shows up, read the last line first, because that line names the real problem. If you are switching into tech or picking up scripting for an IT role, functions are the first tool that makes your code start to look like real software.

Write it once with def

Look again at greet. The word def starts the definition. After it comes the name you choose, then a pair of parentheses, then a colon. The lines below, indented, are the body: the work that runs each time you call the function. The name inside the parentheses, here name, is a placeholder that gets filled in when you call it.

Defining and calling are two different moments

Writing def greet(name): does not run anything. It only teaches Python what greet means. The code runs when you call it, by writing the name with parentheses, like greet(‘Asha’). The value in the parentheses, ‘Asha’, is the argument, and it lands in name for that one call.

Run the file with python greet.py on Windows, or python3 greet.py on macOS and Linux. One copy trap to watch: the straight quotes around ‘Asha’ can arrive as curly quotes when you paste from a web page, and Python stops with a SyntaxError. If that happens, delete them and type the quotes yourself.

WordWhat it means
defkeyword that starts a function definition
parameterthe name in the definition, like name in greet(name)
argumentthe real value you pass, like ‘Asha’ in greet(‘Asha’)
callrunning the function by name with parentheses
bodythe indented lines that run on each call
returnhands a value back to whoever called it
One definition, many calls def greet(name): greet(‘Asha’) greet(‘Ravi’) greet(‘Sam’)

Write the code once, then run it by name as many times as you need.

Getting a value back with return

Greeting people is useful, but most functions do a job and hand you an answer. That is what return is for. It stops the function and sends one value back to the place that called it, so you can store it or use it in more code.

print shows, return gives

add.py
def add(a, b):
    return a + b

total = add(2, 3)
print(total)
print(add(10, 5))
output
5
15

The call add(2, 3) comes back as the number 5, which lands in total. Because add hands back a real value, you can drop it straight into print, or add it to something else, or save it for later. That is the difference from a function that only prints.

Send values in, get one value back total = add(2, 3) total is 5 send 2 and 3 def add(a, b): return a + b hand back 5

Arguments go in, the body runs, one value comes back.

Now the mistake almost everyone makes once. If you forget return, the function still runs, but it hands back nothing useful.

noreturn.py
def add(a, b):
    result = a + b

total = add(2, 3)
print(total)
output
None

The function worked out result, then ended without giving it back, so total is None. None is Python’s word for no value at all. When a function that should give you a number prints None instead, a missing return is nearly always the reason.

Questionprint(x)return x
Puts text on the screenYesNo
Hands a value back to your codeNoYes
Result can be saved in a variableNo, you get NoneYes

Arguments and default values

A function can take more than one argument, and you can give a parameter a default so the caller may skip it. You write the default with an equals sign in the definition.

default.py
def greet(name, hello='Hi'):
    print(hello + ', ' + name)

greet('Asha')
greet('Ravi', 'Welcome')
output
Hi, Asha
Welcome, Ravi

The first call left hello out, so it used the default ‘Hi’. The second call passed ‘Welcome’, which replaced the default for that call only. Defaults are how you keep the common case short while still allowing a change when you need one.

Gotcha: never default a parameter to a listA default value is created once, when Python first reads the def line, not fresh on every call. If that default is a list and the function changes it, the change sticks around for the next call.
trap.py
def add_item(item, bag=[]):
    bag.append(item)
    return bag

print(add_item('pen'))
print(add_item('cup'))
output
['pen']
['pen', 'cup']
The second call was meant to start empty, but it kept the pen from the first call, because both calls share the one list made at definition time. The fix is to default to None and make a fresh list inside.
fixed.py
def add_item(item, bag=None):
    if bag is None:
        bag = []
    bag.append(item)
    return bag
This one line of care saves hours of confused debugging later.

Where a function’s variables live

Names you create inside a function belong to that function. They exist while it runs and are gone once it returns. Try to read one from outside and Python tells you it does not exist.

scope.py
def add(a, b):
    result = a + b
    return result

print(add(2, 3))
print(result)
output
5
Traceback (most recent call last):
  File "scope.py", line 6, in <module>
    print(result)
          ^^^^^^
NameError: name 'result' is not defined

The last line, NameError: name ‘result’ is not defined, is the one to read. The number 5 printed fine, so the function ran. But result lived only inside add, so asking for it outside fails. This is a feature, not an annoyance. It means a name you use inside one function cannot quietly clash with a name somewhere else.

Names inside a function stay inside your program total = 0 a and b do not exist out here inside add() a = 2 b = 3

A function has its own private space for names.

Naming your arguments, and when a function is not worth it

When a function takes several values, a short call like make_line(‘Tea’, 20) reads fine, but a longer one leaves the reader guessing what each value means. You can name arguments right at the call, which makes the intent clear and lets you pass them in any order.

kwargs.py
def make_line(item, price, currency='Rs'):
    return item + ': ' + currency + str(price)

print(make_line('Tea', 20))
print(make_line('Rice', price=60,
                currency='USD'))
output
Tea: Rs20
Rice: USD60

The second call spells out price=60 and currency=’USD’. Those are keyword arguments. They cost a few characters and buy a call anyone can read without opening the definition. For a function with three or more parameters, that trade is usually worth making.

Here is where I step away from a common piece of beginner advice. A lot of tutorials push you to turn every few lines into a function, as if more functions always mean tidier code. They do not. If a snippet appears once and reads clearly where it sits, wrapping it in a function with a name you had to invent can make the file harder to follow, because now the reader has to jump elsewhere to see what it does. Reach for a function when code repeats, when a block deserves a name that explains it, or when it grows long enough to be worth testing on its own. Two plain lines used once are fine left where they are.

A worked example, built step by step

Let us put it together into something small and real: a receipt printer. First a function that formats one line, then a function that prints a whole cart using it.

receipt.py
def make_line(item, price):
    return item + ': ' + str(price)

def show_receipt(items):
    for item, price in items:
        print(make_line(item, price))

cart = [
    ('Tea', 20),
    ('Rice', 60),
]
show_receipt(cart)
output
Tea: 20
Rice: 60

Notice that show_receipt calls make_line. Functions calling functions is normal and good: each one has a single clear job. make_line turns one item and price into a string, and show_receipt loops the cart and prints each formatted line. The str around price turns the number 20 into text so it can join the rest of the line.

Now a common error worth meeting on purpose. Call make_line and forget the price.

oops.py
print(make_line('Tea'))
output
Traceback (most recent call last):
  File "oops.py", line 1, in <module>
    print(make_line('Tea'))
          ^^^^^^^^^^^^^^^^^
TypeError: make_line() missing 1 required positional argument: 'price'

Read the last line. It says make_line() is missing the argument named price. Python is precise here: it even tells you which one you left out. Pass both values and it runs.

Project check-in: two functions take overPart 8 collected expenses as small dictionaries inside a loop, with the adding and the printing tangled into one long block. Now each job gets its own name. add_expense puts a record in the list, and show_expenses prints them.
expenses.py
def add_expense(items, cat, amount, note):
    items.append({
        'category': cat,
        'amount': amount,
        'note': note,
    })

def show_expenses(items):
    for e in items:
        print(e['category'],
              e['amount'],
              e['note'])

expenses = []
add_expense(expenses, 'Food', 250.0, 'lunch')
add_expense(expenses, 'Bus', 40.0, 'to office')
show_expenses(expenses)
print('Entries:', len(expenses))
output
Food 250.0 lunch
Bus 40.0 to office
Entries: 2
The input loop from Part 8 still fits: instead of building the dictionary inline, it now calls add_expense once per entry. Two named jobs, each easy to change on its own. Everything here uses only functions, lists and dictionaries, which you already know.
Why this matters in your first jobReal codebases are functions calling functions. On day one you will read a file full of them and be asked to change just one, or add a new one that others call. A teammate reviewing your work looks for small functions with clear names and a plain return, because that code is easy to test and easy to trust. The habit of pulling repeated lines into a named function is what separates a script that grows into a mess from one that stays workable.
Real interview question"What is the difference between a parameter and an argument, and what does a function return if it has no return statement?" A parameter is the name in the definition, like b in def add(a, b). An argument is the actual value you pass in the call, like the 3 in add(2, 3). A function with no return hands back None. A neat follow up is why a mutable default such as bag=[] is risky, since the default is made once and reused across calls.
Try it yourselfWrite a function area that takes a width and a height and returns their product. Then print the area of a 4 by 3 room and a 5 by 5 room.
task.py
def area(width, height):
    # your code here

print(area(4, 3))
print(area(5, 5))
expected output
12
25
Show solution
solution.py
def area(width, height):
    return width * height

print(area(4, 3))
print(area(5, 5))

What beginners ask about functions

Can a function call another function? Yes, and it is normal. The receipt example did exactly that, with show_receipt calling make_line on every row.

How many values can return hand back? One object. That sounds limiting until you notice the object can be a tuple, so return a, b gives the caller two values at once, which they can unpack.

Do I need a return in every function? No. Some functions just do something, like printing. They hand back None, and that is fine when you are not trying to use the result.

Where should I define my functions? Near the top of the file, above the code that calls them, so the name already exists by the time you use it.

You can now define a function with def, pass it arguments, give a parameter a default, return a value your code can reuse, and keep a function’s own variables from leaking out. That is the core of writing code you do not have to repeat.

Part 10 goes deep on strings and f-strings, and the expense tracker starts printing a clean, aligned receipt instead of bare values side by side. Before then, take the receipt maker above and add a line that prints the total from memory. The full series map lives on the Complete Guide.

Python for Beginners · Part 9 of 20
« Previous: Part 8  |  Complete Guide  |  Next: Part 10

About The Author


Discover more from Journal of Intelligent Infrastructure – By Dr Pranay Jha

Subscribe to get the latest posts sent to your email.

Leave a Reply

Your email address will not be published. Required fields are marked *

Architect’s Toolkit

About the Author

Dr. Pranay Jha is a Cloud and AI Consultant with 18+ years of experience in hybrid cloud, virtualization, and enterprise infrastructure transformation. He specializes in VMware technologies, multi-cloud strategy, and Generative AI solutions. He holds a PhD in Computer Applications with research focused on Cloud and AI, has published multiple research papers, and has been a VMware vExpert since 2016 and a VMUG Community Leader.

Discover more from Journal of Intelligent Infrastructure - By Dr Pranay Jha

Subscribe now to keep reading and get access to the full archive.

Continue reading