4 Functions and Scope

A practical guide to defining, calling, documenting, and designing reusable Python functions while reasoning clearly about parameters, return values, and scope.

Defining and calling reusable behavior

Functions package a sequence of statements into a named, reusable unit. They help divide a program into smaller parts, reduce repetition, clarify intent, and make code easier to test and maintain.

A is defined with def, followed by its name, parentheses, and an indented body. For example, defining greet creates the but does not run its body. Calling greet() executes it. The parentheses are important: greet refers to the object, while greet() calls the .

A can call another . The called must have been defined before that call occurs during program execution.

Takeaway: Defining a makes reusable behavior available; calling it is what executes that behavior.

Inputs: parameters, arguments, and defaults

A names an input in a definition, while an is the value supplied by the caller. For example, in greet_person(name), name is a ; in greet_person("Maya"), "Maya" is an .

Functions may accept several inputs. Arguments can be positional, such as rectangle_area(5, 3), or keyword arguments, such as describe_pet(species="dog", name="Rex"). Keyword arguments make calls more explicit and allow arguments to be supplied in a different order.

A may have a default value. In greet_person(name, punctuation="!"), the default punctuation is used when the caller omits that . Default values are evaluated when the is defined. Avoid mutable defaults such as lists and dictionaries; use None and create a new mutable object inside the when needed.

Takeaway: Clear parameters and arguments make a ’s expected inputs easy to understand and use.

Returning results and ending calls

A is sent back to the caller by the return statement. Returning is different from printing: a printed result appears as output, but a returned result can be assigned to a variable, combined in an expression, or passed to another .

For example, a that returns the square of a number can be used in a larger calculation, such as adding the results of two calls. A may also return multiple values; Python packages them into a tuple, which can be unpacked into separate variables.

A return statement ends the current call. It can appear inside a conditional, which allows a to handle a special case early. If execution reaches the end without a return statement, or reaches a bare return, the returns None.

Takeaway: Return values make functions composable, while printing is primarily for displaying information.

Understanding namespaces and name resolution

A maps names to objects. A is the region in which a name can be accessed without qualification. Each call normally receives its own local , so local variables from one call do not become local variables in another call.

Python generally searches for names in LEGB order:

  1. Local: names inside the current .

  2. Enclosing: names in a surrounding when functions are nested.

  3. Global: names defined at module level.

  4. Built-in: names provided by Python, such as len and print.

Assignments inside a create local names by default, even when a global variable has the same name. A can read a global variable when no local variable hides it, but passing important inputs as parameters usually makes the easier to understand and reuse.

Use global when a must rebind a module-level name, and use nonlocal when a nested must rebind a name from an enclosing . Both should be used sparingly because hidden shared state can make code harder to test and reason about.

Takeaway: Prefer explicit inputs and returned results over hidden changes to global or enclosing state.

Documenting and designing maintainable functions

A is the first statement in a and documents what the does. A useful briefly states the purpose and, when needed, explains parameters, the , side effects, raised exceptions, and input restrictions. Public functions should generally have docstrings, and help() can display them.

means breaking a larger problem into smaller operations, each handled by a focused . In a report-building program, separate functions might read scores, calculate an average, format a report, and coordinate the overall process. Each then has a narrow responsibility and can be reused or tested independently.

A ’s includes its name, parameters, expected inputs, , and possible exceptions. A strong makes assumptions explicit. For example, a discount can validate that a price is not negative and that a percentage falls between the permitted limits.

Avoid both extremes: a that performs many unrelated tasks is difficult to understand, while excessive layers of trivial functions can make a program harder to follow. Create a separate when a section has a meaningful responsibility, is reused, needs independent testing, or makes the surrounding code clearer.

Takeaway: Good documentation and focused interfaces turn into maintainable, testable code.