LEARN PYTHON | Parameters And Variable Scope

Lesson objective

Pass arguments deliberately, explain local and global names, and distinguish reassigning a parameter from changing a shared mutable object.

Learn

04.2 | PARAMETERS AND VARIABLE SCOPE

01 | PARAMETERS ARE LOCAL INPUT NAMES

Consider a function calculating ticket cost. It needs a quantity and a unit price. The definition lists two parameters; each call supplies arguments to bind to those local names. Local means the names belong to that particular call rather than being created as top-level variables.

Different callers can use different names for their data. tickets = 3 and price = 4.5 can be passed into parameters quantity and unit_price. The values matter; matching spelling is unnecessary.

The parameter names help describe the contract. Choose quantity rather than a and unit_price rather than b so a reader can understand the operation without tracing every statement.

def ticket_cost(quantity, unit_price):
    return quantity * unit_price

tickets = 3
price = 4.5
print(ticket_cost(tickets, price))

EXPECTED OUTPUT

13.5

02 | POSITIONAL AND KEYWORD ARGUMENTS

Positional arguments match parameters by their order. ticket_cost(3, 4.5) binds quantity to 3 and unit_price to 4.5. Swapping arguments can create a logic error even when the arithmetic happens to give the same product.

Keyword arguments name the parameter they target: ticket_cost(unit_price=4.5, quantity=3). They improve clarity when several arguments have similar types. A call can combine positional and keyword arguments, with ordinary positional arguments before keyword ones.

Do not supply the same parameter twice, omit a required argument or invent a keyword the function does not accept. Python reports TypeError rather than guessing your intent.

def format_score(name, score):
    return f"{name}: {score}"

print(format_score("Maya", 8))
print(format_score(score=8, name="Maya"))

EXPECTED OUTPUT

Maya: 8
Maya: 8

03 | DEFAULTS PROVIDE AN OPTIONAL INPUT

A default parameter value is used when the caller omits that argument. def greet(name, greeting="Hello") allows greet("Maya") and greet("Maya", "Welcome"). In an ordinary signature, required parameters come before parameters with defaults.

Defaults are evaluated when the definition executes, not afresh for every call. Immutable defaults such as strings or numbers are usually straightforward. A list used as a default can be shared across calls, retaining changes unexpectedly.

Use None as a sentinel for an omitted mutable collection, then create a new list inside the body. Test whether it is None, rather than using if not items, because a caller may deliberately supply an existing empty list.

def add_note(note, notes=None):
    if notes is None:
        notes = []
    notes.append(note)
    return notes

print(add_note("first"))
print(add_note("second"))

EXPECTED OUTPUT

['first']
['second']

04 | LOCAL VARIABLES BELONG TO A CALL

Scope describes where a name can be accessed. A variable assigned inside a function is normally local to that function. When the caller tries to use that local name directly, it is not automatically available. Return the information the caller needs.

Each call has separate local bindings. A second call to the same function does not reuse the earlier call’s local total merely because its spelling is the same. The returned object may live on when the caller keeps a reference to it.

This separation makes functions easier to reason about. You can use a descriptive name total in several independent functions without forcing them to share one top-level value.

def doubled(number):
    total = number * 2
    return total

result = doubled(6)
print(result)
# print(total) here would raise NameError.

EXPECTED OUTPUT

12

05 | THE SAME SPELLING CAN MEAN DIFFERENT BINDINGS

A global name is defined at module level. A function can read such a name when it has no local binding of that name. But assigning a name inside the function normally makes that name local for the whole function body.

If the module has score = 10 and a function assigns score = 3, the local score shadows the global one. Shadowing means the local binding is found in that scope instead of the outer binding. The global score is not replaced by this ordinary local assignment.

Python looks through local, enclosing-function, global and built-in scopes when resolving an ordinary name. This course starts with local and module-level names. Do not assume that names in a separate caller’s local scope are automatically visible to a called function.

score = 10

def show_local_score():
    score = 3
    print("Inside:", score)

show_local_score()
print("Outside:", score)

EXPECTED OUTPUT

Inside: 3
Outside: 10

06 | WHY A GLOBAL UPDATE CAN FAIL

If a function contains score = score + 1 without declaring global score, Python treats score as a local name. Reading that local name before it has been assigned raises UnboundLocalError. It does not first read the module score and then quietly create a new local one.

global score explicitly makes assignments in that function target the module-level binding. It can be appropriate in some designs, but hidden shared state makes reuse and testing harder. Prefer accepting the old value and returning the new value when possible.

The caller then owns the update: score = add_point(score). This clearly shows where data enters and where the result is stored.

def add_point(current_score):
    return current_score + 1

score = 10
score = add_point(score)
print(score)

EXPECTED OUTPUT

11

07 | REBINDING IS DIFFERENT FROM MUTATION

An argument supplies an object reference that is bound to a local parameter. Python does not automatically make a fresh copy of every argument. A list passed to a function can therefore be the same list seen by the caller.

Assigning items = [99] rebinds the local parameter to a new list. Calling items.append(99) changes the original shared list object. These are different operations, even though both mention items.

This behaviour is sometimes summarised as object sharing. It is more precise than saying every argument is copied or that a parameter always acts as an alias for the caller’s variable itself. The local name can be rebound independently.

def replace_locally(items):
    items = [99]

def append_to_shared(items):
    items.append(99)

scores = [8, 5]
replace_locally(scores)
print("After rebind:", scores)
append_to_shared(scores)
print("After append:", scores)

EXPECTED OUTPUT

After rebind: [8, 5]
After append: [8, 5, 99]

08 | MAKE EFFECTS CLEAR IN THE CONTRACT

A function can deliberately change a supplied list, but its caller should know. A name such as append_score and a docstring saying it mutates the list make the effect clearer. An alternative returns a new list, leaving the original unchanged.

A copy of a flat list of numbers is enough for this example. A shallow copy of nested lists still shares inner objects, as Section 3 showed. Copying does not automatically make every level independent.

Treat inputs, returned values and side effects as three separate parts of the design. A function can return None and still have changed data. A function can return a number without changing its inputs.

def with_bonus(scores, bonus):
    return [score + bonus for score in scores]

original = [8, 5]
updated = with_bonus(original, 2)
print(original)
print(updated)

EXPECTED OUTPUT

[8, 5]
[10, 7]

09 | COMPARE LOCAL REBINDING AND SHARED CHANGE

The model begins with caller list [8, 5]. Choose an operation and trace what remains visible after the call. Each run starts afresh, so you can compare effects directly.

10 | DESIGN CALLS YOU CAN EXPLAIN

For each function, identify the parameters, arguments, locals, returned result and any mutation. Trace an ordinary call and a second call with different inputs. For optional lists, test omitted input and an explicitly supplied empty list separately.

Do not fix a scope error by adding global everywhere. Ask which code should own the value. Passing it explicitly and returning a result often produces a smaller, clearer dependency between functions.

A clean function boundary helps the next lesson: when you move a function into another file, it should receive what it needs rather than relying on a local variable that exists only in the importing caller.

PUT YOUR SKILLS TO WORK

Use the Section 4 challenges to practise these ideas independently. Work in your Python editor, predict results and keep a short test record.

Section 4 challenges Python course navigation

MATCH THE PYTHON TERM TO ITS MEANING

Terminology

Terminology

Parameter

A local input name declared by a function definition.

Positional argument

An argument bound according to its position in the call.

Keyword argument

An argument naming the parameter it targets.

Default value

A parameter value used when its argument is omitted.

Scope

The region and rules determining access to a name.

Local name

A name bound within a function call.

Global name

A name at module level.

Shadowing

A local binding hiding an outer binding of the same spelling.

Rebinding

Associating a name with another object.

Mutation

Changing the contents or state of an existing object.

Shared object

An object accessed through more than one reference.

UnboundLocalError

An error from accessing a local name before it has a binding.

NameError

An error when a requested name cannot be resolved.

global

A declaration targeting a module-level name within a function.

Sentinel

A distinguished value signalling a special case, such as None for omitted input.

Mutable default

A default object that can retain changes between calls.

Shallow copy

A new outer collection retaining references to contained objects.

Side effect

An observable action beyond supplying a return value.

Questions

Questions

CHECK YOUR UNDERSTANDING

Select all correct choices. Each exact set earns one point.

1. Which is a keyword argument?
2. A function assigns score = 3 while the module has score = 10. With no global declaration, what changes?
3. Which operations can change a caller’s supplied list?
4. When is a default list object created?
5. Why use if notes is None rather than if not notes?
6. What normally happens when score = score + 1 reads an unassigned local score?
7. Which design avoids a hidden global score update?
8. A function appends to a supplied list but has no return statement. Which can be true?

EXPLAIN AND COMPARE

Write your answers before revealing each sample.

1. Distinguish a parameter from an argument.

2. Explain positional versus keyword arguments.

3. Explain why a default list can retain notes between calls.

4. Explain the None sentinel pattern.

5. Compare local rebinding and shared mutation.

6. Explain UnboundLocalError in score = score + 1.

7. Suggest an alternative to updating a global score.

8. Evaluate copying as a way to avoid effects.

CODE IT | YOUR INDEPENDENT CHALLENGE

Create a pure score update and a deliberately mutating list helper. State the inputs, result and effects of each.

Work in your Python editor. Keep a working version, explain one design choice and record at least two tests. Use the Learn tab for worked examples before comparing your result.

Flashcards

Flashcards

Click to flip. Select the ideas you need to revisit.

0 cards selected for revision.

    Selections are kept while this page is open.

    Workbook

    Workbook

    COMING SOON

    The workbook for Parameters And Variable Scope is coming soon. Complete the matching task and written questions in your exercise book.