{"course":{"slug":"python-for-ai","title":"Python, from Zero, for AI","url":"https://addaly.com/learn/python-for-ai","level":"beginner","track":"build"},"lesson":{"slug":"functions","title":"Functions, and the difference between printing and returning","url":"https://addaly.com/learn/python-for-ai/functions","position":21,"of":89,"minutes":7,"answer":"print shows a value to a person; return hands it to the program, and a function without return gives back None.","keyIdea":"print shows a value to a person; return hands it to the program, and a function without return gives back None."},"body":{"format":"markdown","text":"## Naming a piece of work\n\nA function is a chunk of instructions with a name, so you can run it whenever you like without writing it out again.\n\n```python\ndef word_count(text):\n    return len(text.split())\n\nprint(word_count(\"the rain in spain\"))   # 4\nprint(word_count(\"hello\"))               # 1\n```\n\n`def` starts a definition. `word_count` is the name. `text` is a **parameter**: a name that will be attached to whatever you pass in. The indented block is the body.\n\nDefining a function does not run it. Python reads the `def`, remembers it, and moves on. The body only runs when you call it, with brackets: `word_count(\"hello\")`.\n\n## return hands a value back\n\n```python\ndef area(width, height):\n    return width * height\n\nspace = area(3, 4)\nprint(space)          # 12\nprint(area(2, 5) + area(1, 1))   # 11\n```\n\n`return` does two things: it sends a value back to whoever called the function, and it ends the function immediately. Anything after a `return` that runs is never reached.\n\nBecause the call turns into a value, you can use it anywhere a value fits: store it, add it, pass it to another function.\n\n## print and return are not the same thing\n\nThis is the single biggest confusion at this stage, and it is worth being blunt about.\n\n> Figure: print talks to a person; return talks to the program. def wc(t): print(len(t.split())): Characters appear on the screen; The call's value is None; a = wc(text); a + 1 raises TypeError on NoneType; Nothing can store, add or send the number; A test has nothing to compare against. def wc(t): return len(t.split()): Nothing appears until you print it; The call's value is the number; a = wc(text); a + 1 is 13; The number can go to a file, a request or a screen; assert wc(\"a b\") == 2 is a test. Both show 12 when you try them at the terminal, which is exactly why this confusion survives so long. The difference appears the moment you try to use the answer for anything: a function that prints can only ever feed a screen.\n\n`print` puts characters on a screen for a human. `return` hands a value back to the rest of the program. They look the same when you are testing at the terminal, because in both cases you see `12` appear. They are completely different when the value has to be used.\n\n```python\ndef double_printed(n):\n    print(n * 2)\n\ndef double_returned(n):\n    return n * 2\n\na = double_printed(21)    # prints 42\nb = double_returned(21)   # prints nothing\n\nprint(a)     # None\nprint(b)     # 42\n```\n\nA function with no `return` still gives something back: `None`, Python's word for \"no value\". So `a` is `None`, and `a + 1` fails with `TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'`. When you see `NoneType` in an error, the usual cause is a function that printed instead of returning.\n\nRule of thumb: functions should **return**. Print at the outer edge of your program, where you are actually talking to a person.\n\n## Default values\n\n```python\ndef greet(name, greeting=\"Hello\"):\n    return f\"{greeting}, {name}\"\n\nprint(greet(\"Ivan\"))                   # Hello, Ivan\nprint(greet(\"Ivan\", \"Good morning\"))   # Good morning, Ivan\nprint(greet(name=\"Fatima\"))            # Hello, Fatima\n```\n\nA parameter with a default becomes optional. You can also pass arguments by name, which makes calls with several options readable.\n\n## Names inside stay inside\n\n```python\ndef tax_on(amount):\n    rate = 0.18\n    return amount * rate\n\nprint(tax_on(1000))   # 180.0\nprint(rate)\n```\n\n```\nNameError: name 'rate' is not defined\n```\n\n`rate` lives only while `tax_on` is running. This is a feature. It means you can write a function without worrying about which names the rest of the program has already used.\n\n## A function you will reuse later\n\n```python\ndef build_messages(question, history=None):\n    messages = list(history or [])\n    messages.append({\"role\": \"user\", \"content\": question})\n    return messages\n\nconvo = build_messages(\"Explain gravity in one sentence.\")\nconvo = build_messages(\"Now in Hindi.\", convo)\nprint(len(convo))            # 2\nprint(convo[1][\"content\"])   # Now in Hindi.\n```\n\nThat is the exact structure lesson ten sends to an AI API, built by a function you wrote.\n\n## Try this now\n\nWrite `average(numbers)` that returns the mean of a list, and make it return `0` when the list is empty rather than crashing. Then call it and print the result from outside."},"check":{"question":"A file defines `def double(n):` whose only body line is `print(n * 2)`. Then it runs `result = double(21)` and `print(result + 1)`. What happens?","options":[{"text":"`42` is printed, then the program stops with a TypeError, because `double` displayed a value but handed back None.","correct":true},{"text":"`43` is printed, because printing a value inside a function is how the value gets sent back to the caller.","correct":false,"tempting":"You can see 42 on the screen, so it genuinely looks as though the value made it out of the function and into `result`."},{"text":"`42` is printed and then `43`, because the print shows it and the function still passes the value along.","correct":false,"tempting":"Both things appear to happen in simple tests, so it is easy to believe printing and returning are one action with two visible effects."},{"text":"The program stops before printing anything, because assigning from a function with no return statement is rejected straight away.","correct":false,"tempting":"Some languages really do refuse at compile time when you assign from something that returns nothing, so expecting an early stop is reasonable."}]},"prev":{"slug":"nested-data","title":"Nested data: dictionaries inside lists inside dictionaries","url":"https://addaly.com/learn/python-for-ai/nested-data"},"next":{"slug":"arguments-in-depth","title":"Arguments: positional, named, default, and the trap in the default","url":"https://addaly.com/learn/python-for-ai/arguments-in-depth"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'Functions, and the difference between printing and returning', in Python, from Zero, for AI. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/python-for-ai/functions (Accessed: 31 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"Functions, and the difference between printing and returning","courseTitle":"Python, from Zero, for AI","module":{"index":3,"slug":"functions-and-structure","title":"Functions, modules, and code you can read again in March"},"answer":null,"keyIdea":"print shows a value to a person; return hands it to the program, and a function without return gives back None.","hero":{"kind":"figure","figure":{"t":"figure","label":"Figure 3.1","figure":{"left":{"title":"def wc(t): print(len(t.split()))","items":["Characters appear on the screen","The call’s value is None","a = wc(text); a + 1 raises TypeError on NoneType","Nothing can store, add or send the number","A test has nothing to compare against"]},"type":"compare","right":{"title":"def wc(t): return len(t.split())","items":["Nothing appears until you print it","The call’s value is the number","a = wc(text); a + 1 is 13","The number can go to a file, a request or a screen","assert wc(\"a b\") == 2 is a test"]},"title":"print talks to a person; return talks to the program","caption":"Both show 12 when you try them at the terminal, which is exactly why this confusion survives so long. The difference appears the moment you try to use the answer for anything: a function that prints can only ever feed a screen."},"alt":"Figure: print talks to a person; return talks to the program. def wc(t): print(len(t.split())): Characters appear on the screen; The call’s value is None; a = wc(text); a + 1 raises TypeError on NoneType; Nothing can store, add or send the number; A test has nothing to compare against. def wc(t): return len(t.split()): Nothing appears until you print it; The call’s value is the number; a = wc(text); a + 1 is 13; The number can go to a file, a request or a screen; assert wc(\"a b\") == 2 is a test. Both show 12 when you try them at the terminal, which is exactly why this confusion survives so long. The difference appears the moment you try to use the answer for anything: a function that prints can only ever feed a screen."}},"blocks":[{"t":"h2","id":"naming-a-piece-of-work","runs":[{"text":"Naming a piece of work"}]},{"t":"p","runs":[{"text":"A function is a chunk of instructions with a name, so you can run it whenever you like without writing it out again."}]},{"t":"code","lang":"python","label":"Python","text":"def word_count(text):\n    return len(text.split())\n\nprint(word_count(\"the rain in spain\"))   # 4\nprint(word_count(\"hello\"))               # 1"},{"t":"p","runs":[{"text":"def","code":true},{"text":" starts a definition. "},{"text":"word_count","code":true},{"text":" is the name. "},{"text":"text","code":true},{"text":" is a "},{"text":"parameter","b":true},{"text":": a name that will be attached to whatever you pass in. The indented block is the body."}]},{"t":"p","runs":[{"text":"Defining a function does not run it. Python reads the "},{"text":"def","code":true},{"text":", remembers it, and moves on. The body only runs when you call it, with brackets: "},{"text":"word_count(\"hello\")","code":true},{"text":"."}]},{"t":"h2","id":"return-hands-a-value-back","runs":[{"text":"return hands a value back"}]},{"t":"code","lang":"python","label":"Python","text":"def area(width, height):\n    return width * height\n\nspace = area(3, 4)\nprint(space)          # 12\nprint(area(2, 5) + area(1, 1))   # 11"},{"t":"p","runs":[{"text":"return","code":true},{"text":" does two things: it sends a value back to whoever called the function, and it ends the function immediately. Anything after a "},{"text":"return","code":true},{"text":" that runs is never reached."}]},{"t":"p","runs":[{"text":"Because the call turns into a value, you can use it anywhere a value fits: store it, add it, pass it to another function."}]},{"t":"h2","id":"print-and-return-are-not-the-same-thing","runs":[{"text":"print and return are not the same thing"}]},{"t":"p","runs":[{"text":"This is the single biggest confusion at this stage, and it is worth being blunt about."}]},{"t":"p","runs":[{"text":"print","code":true},{"text":" puts characters on a screen for a human. "},{"text":"return","code":true},{"text":" hands a value back to the rest of the program. They look the same when you are testing at the terminal, because in both cases you see "},{"text":"12","code":true},{"text":" appear. They are completely different when the value has to be used."}]},{"t":"code","lang":"python","label":"Python","text":"def double_printed(n):\n    print(n * 2)\n\ndef double_returned(n):\n    return n * 2\n\na = double_printed(21)    # prints 42\nb = double_returned(21)   # prints nothing\n\nprint(a)     # None\nprint(b)     # 42"},{"t":"p","runs":[{"text":"A function with no "},{"text":"return","code":true},{"text":" still gives something back: "},{"text":"None","code":true},{"text":", Python’s word for “no value”. So "},{"text":"a","code":true},{"text":" is "},{"text":"None","code":true},{"text":", and "},{"text":"a + 1","code":true},{"text":" fails with "},{"text":"TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'","code":true},{"text":". When you see "},{"text":"NoneType","code":true},{"text":" in an error, the usual cause is a function that printed instead of returning."}]},{"t":"p","runs":[{"text":"Rule of thumb: functions should "},{"text":"return","b":true},{"text":". Print at the outer edge of your program, where you are actually talking to a person."}]},{"t":"h2","id":"default-values","runs":[{"text":"Default values"}]},{"t":"code","lang":"python","label":"Python","text":"def greet(name, greeting=\"Hello\"):\n    return f\"{greeting}, {name}\"\n\nprint(greet(\"Ivan\"))                   # Hello, Ivan\nprint(greet(\"Ivan\", \"Good morning\"))   # Good morning, Ivan\nprint(greet(name=\"Fatima\"))            # Hello, Fatima"},{"t":"p","runs":[{"text":"A parameter with a default becomes optional. You can also pass arguments by name, which makes calls with several options readable."}]},{"t":"h2","id":"names-inside-stay-inside","runs":[{"text":"Names inside stay inside"}]},{"t":"code","lang":"python","label":"Python","text":"def tax_on(amount):\n    rate = 0.18\n    return amount * rate\n\nprint(tax_on(1000))   # 180.0\nprint(rate)"},{"t":"code","lang":"","label":"Text","text":"NameError: name 'rate' is not defined"},{"t":"p","runs":[{"text":"rate","code":true},{"text":" lives only while "},{"text":"tax_on","code":true},{"text":" is running. This is a feature. It means you can write a function without worrying about which names the rest of the program has already used."}]},{"t":"h2","id":"a-function-you-will-reuse-later","runs":[{"text":"A function you will reuse later"}]},{"t":"code","lang":"python","label":"Python","text":"def build_messages(question, history=None):\n    messages = list(history or [])\n    messages.append({\"role\": \"user\", \"content\": question})\n    return messages\n\nconvo = build_messages(\"Explain gravity in one sentence.\")\nconvo = build_messages(\"Now in Hindi.\", convo)\nprint(len(convo))            # 2\nprint(convo[1][\"content\"])   # Now in Hindi."},{"t":"p","runs":[{"text":"That is the exact structure lesson ten sends to an AI API, built by a function you wrote."}]},{"t":"h2","id":"try-this-now","runs":[{"text":"Try this now"}],"role":"try"},{"t":"p","runs":[{"text":"Write "},{"text":"average(numbers)","code":true},{"text":" that returns the mean of a list, and make it return "},{"text":"0","code":true},{"text":" when the list is empty rather than crashing. Then call it and print the result from outside."}]}],"check":{"question":[{"text":"A file defines "},{"text":"def double(n):","code":true},{"text":" whose only body line is "},{"text":"print(n * 2)","code":true},{"text":". Then it runs "},{"text":"result = double(21)","code":true},{"text":" and "},{"text":"print(result + 1)","code":true},{"text":". What happens?"}],"options":[{"text":[{"text":"42","code":true},{"text":" is printed, then the program stops with a TypeError, because "},{"text":"double","code":true},{"text":" displayed a value but handed back None."}],"correct":true,"tempting":null},{"text":[{"text":"43","code":true},{"text":" is printed, because printing a value inside a function is how the value gets sent back to the caller."}],"correct":false,"tempting":[{"text":"You can see 42 on the screen, so it genuinely looks as though the value made it out of the function and into "},{"text":"result","code":true},{"text":"."}]},{"text":[{"text":"42","code":true},{"text":" is printed and then "},{"text":"43","code":true},{"text":", because the print shows it and the function still passes the value along."}],"correct":false,"tempting":[{"text":"Both things appear to happen in simple tests, so it is easy to believe printing and returning are one action with two visible effects."}]},{"text":[{"text":"The program stops before printing anything, because assigning from a function with no return statement is rejected straight away."}],"correct":false,"tempting":[{"text":"Some languages really do refuse at compile time when you assign from something that returns nothing, so expecting an early stop is reasonable."}]}],"order":[3,2,1,0],"why":null},"prev":{"slug":"nested-data","title":"Nested data: dictionaries inside lists inside dictionaries","minutes":9},"next":{"slug":"arguments-in-depth","title":"Arguments: positional, named, default, and the trap in the default","minutes":9}}}