Here is a snippet that turns up in a lot of first programs. Two greeting lines, typed out again for every new person.
name = 'Asha'
print('Hello, ' + name)
print('Welcome aboard.')
name = 'Ravi'
print('Hello, ' + name)
print('Welcome aboard.')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.
def greet(name):
print('Hello, ' + name)
print('Welcome aboard.')
greet('Asha')
greet('Ravi')Hello, Asha Welcome aboard. Hello, Ravi Welcome aboard.
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.
| Word | What it means |
|---|---|
| def | keyword that starts a function definition |
| parameter | the name in the definition, like name in greet(name) |
| argument | the real value you pass, like ‘Asha’ in greet(‘Asha’) |
| call | running the function by name with parentheses |
| body | the indented lines that run on each call |
| return | hands a value back to whoever called it |
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
def add(a, b):
return a + b
total = add(2, 3)
print(total)
print(add(10, 5))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.
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.
def add(a, b):
result = a + b
total = add(2, 3)
print(total)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.
| Question | print(x) | return x |
|---|---|---|
| Puts text on the screen | Yes | No |
| Hands a value back to your code | No | Yes |
| Result can be saved in a variable | No, you get None | Yes |
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.
def greet(name, hello='Hi'):
print(hello + ', ' + name)
greet('Asha')
greet('Ravi', 'Welcome')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.
def add_item(item, bag=[]):
bag.append(item)
return bag
print(add_item('pen'))
print(add_item('cup'))['pen'] ['pen', 'cup']
def add_item(item, bag=None):
if bag is None:
bag = []
bag.append(item)
return bagWhere 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.
def add(a, b):
result = a + b
return result
print(add(2, 3))
print(result)5
Traceback (most recent call last):
File "scope.py", line 6, in <module>
print(result)
^^^^^^
NameError: name 'result' is not definedThe 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.
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.
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'))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.
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)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.
print(make_line('Tea'))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.
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))Food 250.0 lunch Bus 40.0 to office Entries: 2
def area(width, height):
# your code here
print(area(4, 3))
print(area(5, 5))12 25
Show solution
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.
References
Python tutorial: Defining functions
Python tutorial: Default argument values
Python FAQ: Why are default values shared between objects


DrJha