Skip to content
VLSI Mentor

Python Part I · Module 11 · Functions

Arguments In, Result Out

A function has two channels to the rest of the program: values go in through its parameters, and one value comes back out through return. Keeping to those two is what makes a function something you can reason about on its own — you can read its header, read its body, and know everything that affects what it does. The moment a function depends on something that arrived by neither route, that stops being true, and this chapter opens with the executed demonstration of what that costs: the same function, given the same argument twice, returning two different answers.

Foundation16 min readPythonPart IFundamentalsFunctionsReturn Values

Part I · Module 11 · Chapter 11.4 · Arguments In, Result Out

1. A Function That Reads Something It Was Not Given

Here is a counter that takes the log as an argument and gets the marker from a name defined above it:

Azvya Education Pvt. Ltd.VLSI Mentor
BROKEN ON PURPOSE
MARKER = "UVM_ERROR"


def count(lines):
    n = 0
    for line in lines:
        if MARKER in line:
            n += 1
    return n


log = ["UVM_ERROR a", "UVM_ERROR b", "UVM_FATAL c"]
print("first call :", count(log))

MARKER = "UVM_FATAL"
print("second call:", count(log))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
first call : 2
second call: 1

Same function. Same argument. Different answers.

Nothing about the call changed — count(log) is written identically on both lines, and log was never touched. What changed was a name the function reads but never declares, and nothing in the call site gives any hint that it exists.

A reader auditing the second call cannot tell what it will return without searching the whole file for assignments to MARKER. That is the cost: the function's header stops being a complete account of its inputs.

2. Pass It In

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
def count(lines, marker):
    n = 0
    for line in lines:
        if marker in line:
            n += 1
    return n


log = ["UVM_ERROR a", "UVM_ERROR b", "UVM_FATAL c"]

print(count(log, "UVM_ERROR"))
print(count(log, "UVM_FATAL"))
print(count(log, "UVM_ERROR"))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
2
1
2

Three calls, three answers that are each decided entirely by what is inside the parentheses. The third call repeats the first and gets the first's answer back, which the Section 1 version could not promise.

The function also became more capable in the process. The version with the hidden name could count one thing; this one counts whatever you ask for.

There is a legitimate exception, and it is narrow: a value that is genuinely constant for the life of the program — a fixed threshold, a fixed banner width — may be read from a module-level name. The distinguishing test is whether anything ever reassigns it. MARKER in Section 1 was reassigned; that is what made it an input in disguise.

3. return Hands a Value Back

return ends the call and supplies the value that the call expression becomes:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
def classify(errors, fatals):
    if fatals > 0:
        return "FATAL"
    if errors > 0:
        return "FAIL"
    return "PASS"


print(classify(0, 0))
print(classify(3, 0))
print(classify(3, 1))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
PASS
FAIL
FATAL

Three return statements, one of which runs. This is a legitimate shape — a function may have as many returns as it has outcomes, and the early ones remove the need for elif chains and nesting.

4. return Exits the Function Immediately

Not just the current block, and not just the current loop — the whole call ends:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
def first_error(log):
    for line in log:
        if "UVM_ERROR" in line:
            return line
        print("checked:", line)
    return ""


print(first_error(["INFO a", "UVM_ERROR b", "INFO c"]))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
checked: INFO a
UVM_ERROR b

checked: printed once, for the one line examined before the match. The loop did not finish, and return "" on the last line was never reached.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
def find_pair(tests, seeds, target):
    for test in tests:
        for seed in seeds:
            if seed == target:
                return test, seed
    return None


print(find_pair(["smoke", "reset"], [4210, 4211], 4211))
print(find_pair(["smoke"], [4210], 9999))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
('smoke', 4211)
None

Two loops, one return, no flag. Chapter 11.9 covers that two-value return properly.

5. No return Means None

A function that never executes a return hands back None:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
def shout(text):
    print(text.upper())


result = shout("hello")
print("returned:", result)
print(type(result))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
HELLO
returned: None
<class 'NoneType'>

The function did something visible and gave the caller nothing. None is Chapter 2.8's "no value at all", arriving here as "this function has no result".

The same happens with a bare return used only to exit early:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
def report(errors):
    if errors == 0:
        return
    print("errors:", errors)


print(report(0))
print(report(3))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
None
errors: 3
None

return with nothing after it means stop here, with no result — and None is what the caller receives.

This chapter is about the mechanism. Whether a function should print or return is a different question, and Chapter 11.8 is entirely about it, because the answer decides whether the function can be used by other code at all.

6. Returning Several Fields

One return hands back one value — but that value can be a collection, so "one value" is not a limit on how much information comes back:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
def parse_log(lines):
    errors = 0
    fatals = 0
    for line in lines:
        if "UVM_FATAL" in line:
            fatals += 1
        elif "UVM_ERROR" in line:
            errors += 1

    if fatals > 0:
        status = "FATAL"
    elif errors > 0:
        status = "FAIL"
    else:
        status = "PASS"

    return {"errors": errors, "fatals": fatals, "status": status}


result = parse_log(["UVM_ERROR @1: a", "UVM_INFO @2: b"])
print(result)
print(result["status"], result["errors"])
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
{'errors': 1, 'fatals': 0, 'status': 'FAIL'}
FAIL 1

A dictionary is the right shape when the fields have names worth keeping — the caller writes result["status"], which says what it means, rather than counting positions. That is Chapter 8.1's whole argument, arriving as a return value.

(The canonical signature for this function is written def parse_log(path) -> dict:. The -> dict part is a type hint, which documents the return type without changing behaviour; Chapter 11.12 covers annotations properly and this chapter leaves them off.)

When the fields have no useful names — a count and a total, say — a tuple is lighter, and Chapter 11.9 covers that.

7. A Function Can Take Nothing and Still Return Something

The two channels are independent. Either can be empty:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
def banner():
    return "=" * 30


def note(text):
    print("note:", text)


print(banner())
note("starting")
print(note("starting"))
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
==============================
note: starting
note: starting
None

banner takes nothing and returns something. note takes something and returns nothing — and the third call shows that None arriving where a value was expected.

8. The Two Channels

InOut
Howparameters, filled by arguments at the callone return value
How manyas many parameters as neededexactly one value — which may be a collection
Declared wherethe def headernowhere; the body decides
If omitteda call with too few arguments is a TypeErrorthe function returns None
Visible to the calleryes, in the headeryes, as the value of the call

The row worth noting is the fourth. A missing argument is caught loudly at the call. A missing return is not caught at all — the caller gets None and finds out later, usually when None is used in arithmetic or a lookup. That asymmetry is why Chapter 11.8 matters as much as it does.

9. Predict, Run, Observe

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
# A
def add(a, b):
    return a + b

print("A:", add(2, 3), add(add(1, 1), 1))

# B
def early(items):
    for item in items:
        return item
    return "empty"

print("B:", early([10, 20]), early([]))

# C
def note(t):
    print("C: note", t)

x = note("hi")
print("C: x is", x)

# D
def stats(nums):
    total = 0
    for n in nums:
        total += n
    return {"count": len(nums), "total": total}

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

The verified output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
A: 5 3
B: 10 empty
C: note hi
C: x is None
D: {'count': 3, 'total': 6}

A — a call is a value, so it nests.

B — the first call returns on the very first pass, so only 10 comes back; the second never enters the loop and falls through to return "empty".

C — the function printed and returned nothing, so x is None.

D — one return, two fields, named by their keys.

10. Exercises

Exercise 1 — find the hidden input. This function's result depends on something not in its header. Name it, then rewrite the function so the header is complete.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
THRESHOLD = 90

def passed(coverage):
    return coverage >= THRESHOLD

Exercise 2 — prove the drift. Using Section 1's program shape, write a function whose answer changes between two identical calls. Print both answers and explain in two sentences why a reader of the call site could not have predicted the second.

Exercise 3 — early return. Write first_failing(results) that takes a list of (name, status) tuples and returns the name of the first failure, or "" if there are none. Verify with a list that has one, and one that has none.

Exercise 4 — count the prints. Take Section 4's first_error and add print("checked:", line) before the if instead of after it. Predict how many checked: lines appear, then verify, and say what that tells you about when return takes effect.

Exercise 5 — None arrives. Write a function that prints a summary and returns nothing, then try to use its result in arithmetic. Record the error and explain which of the two channels was empty.

Exercise 6 — return a record. Write summarise(runtimes) returning a dictionary with count, total and longest. Verify on [120, 450, 90] and on [], and say what your function does for the empty case.

Exercise 7 — leave two loops. Rewrite Chapter 10.11 Section 6's flag-and-two-breaks program as a function using one return. Count the lines removed.

Exercise 8 — complete the header. For each description, write only the def line: (a) counts a given marker in given lines (b) decides pass or fail from an error count and a fatal count (c) formats a test name and a seed into a job id (d) returns a fixed separator.

11. Summary

  • A function has two channels: values in through parameters, one value out through return.
  • Everything the result depends on should arrive as an argument. Otherwise the same call can return different answers at different times.
  • A complete header is the point — reading def count(lines, marker): should tell you everything the function needs.
  • A genuinely constant module-level value is the narrow exception. The test is whether anything ever reassigns it.
  • Passing the hidden input in usually makes the function more capable, not just safer.
  • return ends the call immediately — not just the block, not just the loop, but every enclosing loop too.
  • That makes return the clean answer to leaving a nested search, replacing Chapter 10.11's flag and second break.
  • A function may have several return statements, one per outcome.
  • No return means None, and so does a bare return.
  • One value can be a collection, so a dictionary returns several named fields at once.
  • A missing argument is a loud TypeError; a missing return is silent.

The next chapter makes some of those arguments optional.

Where this fits

Part of the Python curriculum.