What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

Putting code inside a Python function can change what its names refer to and what result reaches the caller. A function gives a block of behavior a name and a separate local scope; calling it runs the block, but printing a value is not the same as returning it. Those differences explain many surprises when code is moved into a function.

What changes when you define and call a function?

The Python tutorial states that “The keyword def introduces a function definition.” A def statement creates a function object and binds it to a name. The body does not run just because the definition is read; it runs when the function is called.

def double(number):
    return number * 2

answer = double(4)

Here, double is the function name, number is a parameter, and 4 is the argument passed by the caller. The call runs the body and assigns its returned value, 8, to answer. A function can also be called from multiple places, so one implementation can replace repeated inline behavior.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Repeated code versus a function

Approach Duplication Readability and reuse
Repeat the calculation inline Each copy must be changed if the calculation changes. Can be straightforward for a one-off operation, but repeated blocks make consistency harder to maintain.
Put the calculation in a function Keep one implementation and call it where needed. A meaningful name can clarify intent and makes the behavior reusable, provided the function has a clear purpose.

A function is a structural tool, not an automatic performance improvement. For a tiny operation used once, adding a function may not make the code clearer. Its value is strongest when a named behavior is reused or gives a useful boundary to the program.

Why does a value print but not come back?

print() displays output; return supplies a result to the code that called the function. If a function finishes without a return expression, Python returns None.

def show_total(a, b):
    print(a + b)

result = show_total(2, 3)  # displays 5; result is None

def calculate_total(a, b):
    return a + b

result = calculate_total(2, 3)  # result is 5

If another part of the program needs to store, compare, or further calculate with the result, return it. Keep printing inside the function when displaying output is itself the intended behavior. When refactoring, check whether the original statements printed a value, changed an object, or computed a value; moving them into a function does not turn those actions into a return automatically.

Why is my variable different inside a function?

Each function call has a local namespace. When Python looks up a name, it checks the function’s local names first, then enclosing function scopes, the module’s global namespace, and built-ins. An assignment inside a function binds a local name by default, rather than changing a caller’s variable with the same spelling.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
total = 10

def set_total():
    total = 20  # local to this function call

set_total()
print(total)  # 10

The function’s total is local, so the module-level total remains 10. This is often a useful safety boundary: a function can use temporary names without unexpectedly rebinding names elsewhere. Python provides global and nonlocal for particular cases where assignments should bind outside the local scope, but returning a result is usually a clearer interface.

Rebinding a parameter is not the same as changing an object

Python passes arguments by assignment: a parameter becomes a local name referring to the object supplied by the caller. Rebinding that local name does not rebind the caller’s name. But if the object is mutable, an in-place change can be visible through the caller’s reference.

def rebind(items):
    items = ["new"]  # local name now refers to a different list

def append_item(items):
    items.append("new")  # changes the original list in place

values = ["old"]
rebind(values)
print(values)  # ["old"]
append_item(values)
print(values)  # ["old", "new"]

The first function only changes what its local parameter refers to. The second mutates the shared list object, so the caller observes the added item. If a function needs to communicate several output values, returning them together can make the interface explicit; the Python Programming FAQ describes returning a tuple as “almost always the clearest solution.”

Why can a default value persist between calls?

Python evaluates a default argument expression once, when the def statement executes—not each time the function is called. If that default is a mutable object such as a list, calls that use the default share the same object.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
def add_name(name, names=[]):
    names.append(name)
    return names

print(add_name("Ada"))   # ["Ada"]
print(add_name("Grace")) # ["Ada", "Grace"]

Use None when each call should get a fresh list, then create it inside the function:

def add_name(name, names=None):
    if names is None:
        names = []
    names.append(name)
    return names

In this version, calls that omit names each create a new list. A caller can still pass an existing list when sharing or updating that list is intended.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

How should I choose function inputs and outputs?

Parameters define the inputs a function accepts; arguments are the values supplied at a call site. Positional and keyword arguments are common ways to provide them. Python also supports positional-only and keyword-only parameters. Keyword-only parameters can help make calls clearer when a function has several optional settings.

def format_price(amount, *, currency="USD"):
    return f"{currency} {amount:.2f}"

label = format_price(12, currency="CAD")

The * makes currency keyword-only, so the caller must spell out that setting. Choose names and defaults that make the intended inputs apparent; keep a function’s job and interface focused enough that a caller can understand what it does and what it returns.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Function annotations are optional metadata, not runtime type enforcement. The Python tutorial says annotations are stored in __annotations__ and otherwise do not affect the function.

What to check when a refactor changes behavior

  • Names: Does an assignment now create a local variable instead of changing a name outside the function?
  • Results: Does the caller need a value? If so, does the function return it, or only print it?
  • Mutable objects: Is the function rebinding its parameter, or changing the passed object in place?
  • Defaults: Is a mutable default being reused across calls?
  • Purpose: Does the function name describe one clear behavior, and is that behavior genuinely useful to reuse or isolate?

For the language rules, see the Python Software Foundation’s Python 3.14.8 tutorial section on defining functions and its Programming FAQ entry on output parameters.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.