Skip to content
VLSI Mentor

Python Part I · Module 11 · Functions

return Is Not print

Two functions can look almost identical, differ by one word, and be worlds apart in usefulness. One prints its answer; the other returns it. On the screen they appear to do the same thing, which is exactly why the mistake is so easy to make and so hard to notice — the first version looks like it works. It does not. A function that prints its answer has not produced an answer; it has produced an effect on your terminal, and the program it was called from got nothing. This is the most consequential distinction in the module, and it is worth more than agreement — it is worth seeing fail in four different ways.

Foundation16 min readPythonPart IFundamentalsFunctionsReturn Values

Part I · Module 11 · Chapter 11.8 · return Is Not print

1. Two Functions That Look the Same

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
def count_printing(log):
    errors = 0
    for line in log:
        if "UVM_ERROR" in line:
            errors += 1
    print(errors)


def count_returning(log):
    errors = 0
    for line in log:
        if "UVM_ERROR" in line:
            errors += 1
    return errors


log = ["UVM_ERROR @1: a", "UVM_INFO @2: b", "UVM_ERROR @3: c"]

count_printing(log)
print(count_returning(log))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
2
2

The same 2 twice. On screen they are indistinguishable, and that is the entire problem — the first version appears to work.

2. One Hands a Value Back; the Other Does Not

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
print(value)     shows something to a HUMAN, on a terminal
return value     hands something to the CODE that called the function

They are not two styles of output. They are output to two completely different audiences, and only one of them is a program.

print writes characters to the terminal and produces no value. return produces a value and writes nothing. A function that prints its answer has told a person and told the program nothing.

3. The Caller Gets None

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
def count_printing(log):
    errors = 0
    for line in log:
        if "UVM_ERROR" in line:
            errors += 1
    print(errors)


log = ["UVM_ERROR @1: a", "UVM_ERROR @2: b"]

result = count_printing(log)
print("result is:", result)
print("type:", type(result))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
2
result is: None
type: <class 'NoneType'>

The 2 on the first line went to the terminal. The variable result — the thing the rest of the program would actually use — holds None, because Chapter 11.4 Section 5 established that a function with no return hands back None.

So the number exists on your screen and nowhere else in the program.

4. It Does Not Compose

Chapter 11.3 Section 8 showed that a call is a value, so calls nest. That property vanishes the moment a function prints instead of returning:

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
def count_printing(log):
    total = 0
    for line in log:
        if "UVM_ERROR" in line:
            total += 1
    print(total)


a = ["UVM_ERROR @1: a"]
b = ["UVM_ERROR @2: b", "UVM_ERROR @3: c"]

print(count_printing(a) + count_printing(b))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1
2
TypeError: unsupported operand type(s) for +: 'NoneType' and 'NoneType'

Read the output carefully. The 1 and the 2 printed — both functions ran and both knew their answers. Then the addition failed, and the message names the reason exactly: 'NoneType' and 'NoneType'. Neither call produced a value. The two numbers on screen were never in the program.

The returning version composes without comment:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
def count_errors(log):
    total = 0
    for line in log:
        if "UVM_ERROR" in line:
            total += 1
    return total


a = ["UVM_ERROR @1: a"]
b = ["UVM_ERROR @2: b", "UVM_ERROR @3: c"]

print(count_errors(a) + count_errors(b))
print(count_errors(a) > 0)
print([count_errors(a), count_errors(b)])
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
3
True
[1, 2]

Added, compared, collected into a list. None of those three lines can be written against a function that prints.

5. The Answer Cannot Be Kept

A returned value can be stored, accumulated, sorted, compared, or written to a report. A printed one has already left the program.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
def count_errors(log):
    total = 0
    for line in log:
        if "UVM_ERROR" in line:
            total += 1
    return total


logs = {
    "axi_smoke": ["UVM_ERROR @1: a", "UVM_ERROR @2: b"],
    "axi_burst": ["UVM_INFO @1: ok"],
    "reset_seq": ["UVM_ERROR @1: x"],
}

counts = {}
for name in logs:
    counts[name] = count_errors(logs[name])

worst = ""
worst_count = -1
for name in counts:
    if counts[name] > worst_count:
        worst_count = counts[name]
        worst = name

print(counts)
print("worst:", worst, "with", worst_count)
print("total:", counts["axi_smoke"] + counts["axi_burst"] + counts["reset_seq"])
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
{'axi_smoke': 2, 'axi_burst': 0, 'reset_seq': 1}
worst: axi_smoke with 2
total: 3

Three different uses of the same answers, and the accumulator and max-tracking patterns from Chapter 10.10 doing the work. With a printing version, none of this is reachable — the numbers appeared on screen in a fixed order and were gone.

6. It Cannot Be Reused Where Printing Is Wrong

Some callers must not print at all:

  • a script with a --quiet flag
  • a function assembling a report file rather than terminal output
  • a test checking that count_errors gives 2 for a known log
  • any code that calls it in a loop over four thousand logs, where four thousand stray lines would bury the actual summary

A returning function works in every one of those. A printing function forces its output on all of them, and the only fix is to change the function — which means every other caller is affected too.

7. Where print Is Exactly Right

This chapter is not an argument against print. It is an argument about which layer prints.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
def count_errors(log):
    total = 0
    for line in log:
        if "UVM_ERROR" in line:
            total += 1
    return total


def classify(count):
    if count == 0:
        return "PASS"
    return "FAIL"


logs = {"axi_smoke": ["UVM_ERROR @1: a"], "axi_burst": ["UVM_INFO @1: ok"]}

for name in logs:
    count = count_errors(logs[name])
    print(name, "->", classify(count), "(" + str(count) + " errors)")
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
axi_smoke -> FAIL (1 errors)
axi_burst -> PASS (0 errors)

count_errors and classify compute and return. The loop at the bottom prints — and that is correct, because its entire job is to show a person the result.

The rule, stated properly:

Functions that compute a result return it. One layer at the edge of the program turns results into output.

That edge layer is allowed to print as much as it likes, because printing is what it is for. Chapter 11.15 turns this split into a deliberate architecture; here it is enough to know that "never print in a function" is the wrong lesson and "do not print instead of returning" is the right one.

8. Do Not Do Both

The tempting compromise is to print and return:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
def count_errors(log):
    total = 0
    for line in log:
        if "UVM_ERROR" in line:
            total += 1
    print("counted", total, "errors")
    return total


a = count_errors(["UVM_ERROR @1: a"])
b = count_errors(["UVM_ERROR @1: a", "UVM_ERROR @2: b"])
print("total:", a + b)
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
counted 1 errors
counted 2 errors
total: 3

It works, and it has quietly reintroduced Section 6's problem. This function still cannot be used by anything that must stay quiet, and calling it four thousand times produces four thousand lines nobody asked for.

The same reasoning as Chapter 11.7 Section 8's rule against mutating and returning: a function should have one effect that the caller can see. If you want progress output during a long run, that is a decision for the calling layer, which can print as it goes around its own loop.

9. The Two Channels, Compared

Questionprint(x)return x
Who receives ita human, on a terminalthe calling code
What the caller getsNonethe value
Can the result be storednoyes
Can calls be nestednoyes
Can it be totalled or comparednoyes
Can it be checked by a testnoyes
Usable in a quiet contextnoyes
Right place for itone layer, at the edgeevery function that computes

Every "no" in that column is a thing the function cannot do, permanently, until it is rewritten.

10. Predict, Run, Observe

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# A
def p(n):
    print(n * 2)

def r(n):
    return n * 2

x = p(5)
y = r(5)
print("A:", x, y)

# B
def half(n):
    return n // 2

print("B:", half(half(20)))

# C
def shout(t):
    print(t.upper())

print("C:", shout("hi"))

# D
def tally(items):
    n = 0
    for i in items:
        n += 1
    return n

print("D:", tally([1, 2]) + tally([3]))

The verified output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
10
A: None 10
B: 5
HI
C: None
D: 3

A — p(5) printed 10 before anything else; its result is None. r(5) printed nothing and its result is 10.

B — nesting works because half returns.

C — two lines from one statement, and neither is what you would expect. shout("hi") ran first and printed HI with no prefix, because that print is inside the function and knows nothing about the "C:" label. Then the outer print showed what shout handed back — C: None. Two lines from one call, with the label attached to the wrong one, is the signature of this bug.

D — two returned values added. The printing equivalent would be the Section 4 TypeError.

11. Exercises

Exercise 1 — convert it. Rewrite this so it can be used by other code, and show a call that would have been impossible before.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
def average(numbers):
    total = 0
    for n in numbers:
        total += n
    print(total / len(numbers))

Exercise 2 — the None. Call a printing function, store its result, and print the stored value and its type. Explain the two lines of output in two sentences.

Exercise 3 — break composition. Write two printing functions and try to add their results. Record the exact error and say which part of the message identifies the cause.

Exercise 4 — keep the answers. Using a returning count_errors, build a dictionary of name to count for three logs, then print only the name with the most errors. Say why the printing version could not do this.

Exercise 5 — find the edge. In a three-function script of your own, identify which function should be the only one that prints. Explain your choice in two sentences.

Exercise 6 — both at once. Take Section 8's function and name two callers for which its printing is a problem. Then move the printing to where it belongs.

Exercise 7 — the quiet test. Write a check that count_errors returns 2 for a known three-line log, printing only ok or wrong. Explain in one sentence why a printing count_errors makes this impossible.

Exercise 8 — classify these. For each, say whether the function should print, return, or both, and why: (a) counts failures in a log (b) renders the final summary table (c) decides a pass/fail verdict from counts (d) shows a progress line every hundred jobs (e) formats a test name and seed into a job id.

12. Summary

  • print shows a human something; return hands a value to the calling code. Different audiences, not different styles.
  • A printing function hands its caller None. The answer exists on screen and nowhere in the program.
  • On screen the two versions look identical, which is why the mistake survives the first use.
  • A printing function does not compose. f(a) + f(b) is a TypeError on NoneType.
  • Its answer cannot be stored, totalled, compared, sorted or written to a report.
  • It cannot be tested, because there is no value to check.
  • It cannot be reused anywhere that must stay quiet — a --quiet flag, a report builder, a loop over four thousand logs.
  • The failure arrives at the second use, not the first, and the fix touches every call site.
  • Printing is correct at the edge. Functions that compute return; one layer turns results into output.
  • Do not do both. A function that prints and returns is still unusable in a quiet context.

The next chapter removes the last reason people reach for printing: needing to hand back more than one thing.

Where this fits

Part of the Python curriculum.