Make data flow explicit
The scope of a variable is the part of a program where that name is available. Function parameters and variables assigned inside a function are normally local: they belong to that function call. Names assigned outside functions are global to that module.
Local variables exist inside their function
Both parameters and other variables created in a function are local to it. They can be used while the function is running, but they do not become available elsewhere merely because the function was called:
def count_records(records):
total = len(records)
print(total)
count_records([{"title": "A"}, {"title": "B"}])
print(total)2 NameError: name 'total' is not defined
The name total exists only inside count_records. If another part of the program needs the value, the function should return it:
def count_records(records):
total = len(records)
return total
total = count_records([{"title": "A"}, {"title": "B"}])
print(total)The two uses of the name total here are still separate variables: one local to count_records, and one assigned outside the function from its return value.
Local names can mask global names
A function can read a module-level variable if it has not created a local variable with the same name:
minimum_year = 1900
def included(year):
return year >= minimum_year
print(included(1952))This works, but the function has a hidden input: understanding it requires finding minimum_year elsewhere in the module.
Assignment changes how Python interprets a name. In the next example, minimum_year inside the function is a new local variable which masks the global one:
def choose_limit():
minimum_year = 1950
print(minimum_year)
minimum_year = 1900
choose_limit()
print(minimum_year)1950 1900
Python treats a name assigned anywhere in a function as local throughout that function unless told otherwise. Trying to read it before the local assignment therefore fails:
minimum_year = 1900
def choose_limit():
print(minimum_year)
minimum_year = 1950
choose_limit()UnboundLocalError: local variable 'minimum_year' referenced before assignment
The error does not mean the global value is missing. It means the later assignment caused Python to interpret this particular minimum_year as local before it had a value.
What global changes
The global keyword declares that assignments in a function should target a module-level variable instead of creating a local one:
processed_records = 0
def record_processed():
global processed_records
processed_records += 1
record_processed()
print(processed_records)1
Without global, the assignment would target a local name. The keyword is important to recognise when reading code, but changing shared state this way makes inputs and outputs harder to follow. Most pipeline values should travel through parameters and return values instead.
The following function uses that explicit pattern. Its source records and selected language arrive as parameters, and its result leaves through return:
def count_language(records, language):
count = 0
for record in records:
if record["language"] == language:
count += 1
return count
records = [
{"title": "A", "language": "Finnish"},
{"title": "B", "language": "Swedish"},
]
print(count_language(records, "Finnish"))The names records, language and count inside count_language are local. The separate records in the final part of the program is global. Passing it as an argument makes the connection visible.
A maintenance warning: hidden input
Generated code sometimes depends on a global value without making that dependency clear:
selected_language = "Finnish"
def matching_records(records):
matches = []
for record in records:
if record["language"] == selected_language:
matches.append(record)
return matchesTo understand this function, you have to search elsewhere for selected_language. A caller also cannot select a different language without first changing shared state. Make the input explicit instead:
def matching_records(records, selected_language):
matches = []
for record in records:
if record["language"] == selected_language:
matches.append(record)
return matchesThis small repair matters in analysis pipelines. A dashboard filter, a code cell in a notebook or a test can now pass its selected value directly, and the returned records can be inspected before presentation.
When reviewing unfamiliar code, ask:
- Which values enter this function as parameters?
- Which values are created locally?
- Which values are read from outside the function?
- Does the function return the transformed data, or silently change shared state?
The global keyword can make assignment target a module-level name, but it is rarely the right way to move analytical data between pipeline stages. Prefer parameters for inputs and return values for outputs. Module-level constants, such as a fixed column-name list, are reasonable when they truly are configuration rather than changing data.
Main functions keep orchestration local
An explicit main function can connect the stages without making intermediate values global:
def main():
records = read_records("collections.csv")
selected = matching_records(records, "Finnish")
rows = summarize(selected)
print_rows(rows)
if __name__ == "__main__":
main()Here records, selected and rows are local to main. Each pipeline function receives the data it needs and returns the value used by the next stage. This is the shape to look for when an agent proposes a multi-function program.
You can check your current points from the blue blob in the bottom-right corner of the page.