When you're deep into rapid prototyping, it's tempting to skip clean scoping or reuse common variable names (hello, df
!), thinking it will save time. But this can lead to sneaky errors that disrupt your workflow.
The good news? Writing clean, well-defined code requires no additional effort once the basic principles are understood.
Let's analyze it.
Think of a variable as a container that will store some information. Scope refers to the region of your code where a variable can be accessed.
Scoping prevents accidental changes by limiting where variables can be read or modified. If all variables are accessible from anywhere, you will need to keep track of all of them to avoid accidentally overwriting them.
In Python, the scope is defined by the LEGB rulewhat does it mean: Local, surrounding, global and integrated..
Let's illustrate this with an example.
# Global scope, 7% tax
default_tax = 0.07 def calculate_invoice(price):
# Enclosing scope
discount = 0.10
total_after_discount = 0
def apply_discount():
nonlocal total_after_discount
# Local scope
tax = price * default_tax
total_after_discount = price - (price * discount)
return total_after_discount + tax
final_price = apply_discount()
return final_price, total_after_discount
# Built-in scope
print("Invoice total:", round(calculate_invoice(100)(0), 2))
1. Local level
Variables inside a function are in the local scope. They can only be accessed within that function.
In the example, tax
is a local variable inside apply_discount
. It is not accessible outside of this function.
2. Scope attached
These refer to variables in a function that contains a nested function. These variables are not global but can be accessed through the internal (nested) function. In this example, discount
and total_after_discount
are variables in the attached scope of apply_discount
.
He nonlocal
keyword:
He nonlocal
keyword is used to modify variables in the attached scope, not just read them.
For example, suppose you want to update the variable total_after_discount
which is in the attached scope of the function. Without nonlocal
if you assign to total_after_discount
inside the internal function, Python will treat it as a new local variable.indeed shade the outside variable. This can introduce errors and unexpected behavior.
3. Global reach
Variables that are defined out of all functions and accessible everywhere.
He global
statement
When you declare a variable like global
Inside a function, Python treats it as a reference to the variable outside the function. This means that the changes will affect the variable in the global scope.
With the global
keyword, Python will create a new local variable.
x = 10 # Global variabledef modify_global():
global x # Declare that x refers to the global variable
x = 20 # Modify the global variable
modify_global()
print(x) # Output: 20. If "global" was not declared, this would read 10
4. Built-in scope
Refers to the reserved keywords that Python uses for its built-in functions, such as print
, def
, round
etc. This can be accessed at any level.
Both keywords are crucial for modifying variables in different scopes, but they are used differently.
global
: Used to modify variables in the global scope.nonlocal
: Used to modify variables in the attached (non-global) scope.
Variable shadowing occurs when a variable in an internal scope hides a variable in an external scope.
Within the inner scope, all references to the variable will point to the inner variable, not the outer one. This can lead to confusion and unexpected results if you are not careful.
Once execution returns to the outer scope, the inner variable ceases to exist and any reference to the variable will point to the outer scope variable.
Here's a quick example. x
is shaded in each scope, resulting in different results depending on the context.
#global scope
x = 10def outer_function():
#enclosing scope
x = 20
def inner_function():
#local scope
x = 30
print(x) # Outputs 30
inner_function()
print(x) # Outputs 20
outer_function()
print(x) # Outputs 10
A similar concept to variable shadowing, but this occurs when a local variable redefines or overrides a parameter passed to a function.
def foo(x):
x = 5 # Shadows the parameter `x`
return xfoo(10) # Output: 5
x
is passed as 10. But it is immediately shadowed and overwritten by x=5
Each recursive call gets your own execution contextmeaning that local variables and parameters in that call are independent of previous calls.
However, if a variable is modified globally or passed explicitly as a parameter, the change can influence subsequent recursive calls.
- local variables: These are defined within the function and only affect the current recursion level. They do not persist between calls.
- Parameters passed explicitly to the next recursive call they retain their values from the previous call, allowing recursion to accumulate state at all levels.
- global variables: These are shared at all levels of recursion. If modified, the change will be visible to all levels of recursion.
Let's illustrate this with an example.
Example 1: Using a global variable (not recommended)
counter = 0 # Global variabledef count_up(n):
global counter
if n > 0:
counter += 1
count_up(n - 1)
count_up(5)
print(counter) # Output: 5
counter
is a global variable shared between all recursive calls. It is incremented at each level of recursion and its final value (5) is printed once the recursion completes.
Example 2: Using parameters (recommended)
def count_up(n, counter=0):
if n > 0:
counter += 1
return count_up(n - 1, counter)
return counterresult = count_up(5)
print(result) # Output: 5
counter
now it is a parameter of the function.counter
it is passed from one level of recursion to the next, with its value updated at each level. Hecounter
It is not reset on each call, but is The current state is passed to the next level of recursion..- The function is now pure — has no side effects and only acts within its own scope.
- When the recursive function returns, the
counter
“goes up” to the higher level and returns to the base case.
1. Use descriptive variable names
Avoid vague names like df
either x
. Use descriptive names like customer_sales_df
either sales_records_df
for clarity.
2. use capital letters
for constants
This is the standard naming convention for constants in Python. For example, MAX_RETRIES = 5
.
3. Avoid global variables as much as possible.
Global variables introduce bugs and make the code more difficult to test and maintain. It is best to explicitly pass variables between functions.
4. Try to write pure functions whenever possible.
What is a pure function?
- deterministic: Always produces the same output for the same input. It is not affected by external states or randomness.
- No side effects: Does not modify any variable or external state. It operates only within its local scope.
Wearing nonlocal
either global
would make the function impure.
However, if you are working with a closingyou should use the nonlocal
keyword to modify variables in the surrounding (external) scope, which helps avoid variable shadowing.
TO closing occurs when a nested function (inner function) captures and references variables from its attached function (outer function). This allows the inner function to “remember” the environment in which it was created, including accessing variables from the scope of the outer function, even after the outer function has finished executing.
The concept of closures can be very profound, so let me know in the comments if this is something I should delve into in the next article! 🙂
5. Avoid variable shadowing and parameter shadowing.
If you need to reference an external variable, avoid reusing its name in an internal scope. Use different names to clearly distinguish variables.
That's all! Thank you for staying with me until the end.
Have you encountered any of these challenges in your own work? Leave your thoughts in the comments below!
I write regularly about Python, software development, and the projects I build, so follow me so you don't miss out. See you in the next article 🙂