Why Variable Scoping Can Make or Break Your Information Science Workflow | by Clara Chong | Jan, 2025

Let’s kick off 2025 by writing some clear code collectively

Picture by Swello from Unsplash

Whenever you’re deep in fast prototyping, it’s tempting to skip clear scoping or reuse frequent variable names (good day, df!), pondering it’ll save time. However this could result in sneaky bugs that break your workflow.

The excellent news? Writing clear, well-scoped code doesn’t require extra effort when you perceive the fundamental rules.

Let’s break it down.

Consider a variable as a container that can retailer some data. Scope refers back to the area of your code the place a variable is accessible.

Scope prevents unintended modifications by limiting the place variables will be learn or modified. If each variable was accessible from anyplace, you’ll must preserve monitor of all of them to keep away from overwriting it unintentionally.

In Python, scope is outlined by the LEGB rule, which stands for: native, enclosing, world and built-in.

Scoping in Python, referred as LEGB (by creator)

Let’s illustrate this with an instance.

# International scope, 7% tax
default_tax = 0.07

def calculate_invoice(value):
# Enclosing scope
low cost = 0.10
total_after_discount = 0

def apply_discount():
nonlocal total_after_discount

# Native scope
tax = value * default_tax
total_after_discount = value - (value * low cost)
return total_after_discount + tax

final_price = apply_discount()
return final_price, total_after_discount

# Constructed-in scope
print("Bill complete:", spherical(calculate_invoice(100)[0], 2))

1. Native scope

Variables inside a operate are within the native scope. They’ll solely be accessed inside that operate.

Within the instance, tax is a neighborhood variable inside apply_discount. It’s not accessible exterior this operate.

2. Enclosing scope

These discuss with variables in a operate that comprises a nested operate. These variables usually are not world however will be accessed by the inside (nested) operate. On this instance, low cost and total_after_discount are variables within the enclosing scope of apply_discount .

The nonlocal key phrase:

The nonlocal key phrase is used to modify variables within the enclosing scope, not simply learn them.

For instance, suppose you need to replace the variable total_after_discount, which is within the enclosing scope of the operate. With out nonlocal, when you assign to total_after_discount contained in the inside operate, Python will deal with it as a brand new native variable, successfully shadowing the outer variable. This may introduce bugs and surprising conduct.

3. International scope

Variables which can be outlined exterior all features and accessible all through.

The world assertion

Whenever you declare a variable as world inside a operate, Python treats it as a reference to the variable exterior the operate. Which means modifications to it’ll have an effect on the variable within the world scope.

With the world key phrase, Python will create a brand new native variable.

x = 10  # International variable

def modify_global():
world x # Declare that x refers back to the world variable
x = 20 # Modify the worldwide variable

modify_global()
print(x) # Output: 20. If "world" was not declared, this could learn 10

4. Constructed-in scope

Refers back to the reserved key phrases that Python makes use of for it’s built-in features, similar to print , def , spherical and so forth. This may be accessed at any degree.

Each key phrases are essential for modifying variables in numerous scopes, however they’re used otherwise.

  • world: Used to change variables within the world scope.
  • nonlocal: Used to change variables within the enclosing (non-global) scope.

Variable shadowing occurs when a variable in an inside scope hides a variable from an outer scope.

Throughout the inside scope, all references to the variable will level to the inside variable, not the outer one. This may result in confusion and surprising outputs when you’re not cautious.

As soon as execution returns to the outer scope, the inside variable ceases to exist, and any reference to the variable will level again to the outer scope variable.

Right here’s a fast instance. x is shadowed in every scope, leading to totally different outputs relying on the context.

#world scope
x = 10

def outer_function():
#enclosing scope
x = 20

def inner_function():
#native scope
x = 30
print(x) # Outputs 30

inner_function()
print(x) # Outputs 20

outer_function()
print(x) # Outputs 10

An analogous idea to variable shadowing, however this happens when a neighborhood variable redefines or overwrites a parameter handed to a operate.

def foo(x):
x = 5 # Shadows the parameter `x`
return x

foo(10) # Output: 5

x is handed as 10. However it’s instantly shadowed and overwritten by x=5

Every recursive name will get its personal execution context, which means that the native variables and parameters in that decision are unbiased of earlier calls.

Nonetheless, if a variable is modified globally or handed down explicitly as a parameter, the change can affect subsequent recursive calls.

  • Native variables: These are outlined contained in the operate and solely have an effect on the present recursion degree. They don’t persist between calls.
  • Parameters handed explicitly to the subsequent recursive name retain their values from the earlier name, permitting the recursion to build up state throughout ranges.
  • International variables: These are shared throughout all recursion ranges. If modified, the change shall be seen to all ranges of recursion.

Let’s illustrate this with an instance.

Instance 1: Utilizing a worldwide variable (not advisable)

counter = 0  # International variable

def count_up(n):
world counter
if n > 0:
counter += 1
count_up(n - 1)

count_up(5)
print(counter) # Output: 5

counter is a worldwide variable shared throughout all recursive calls. It will get incremented at every degree of recursion, and its remaining worth (5) is printed after the recursion completes.

Instance 2: Utilizing parameters (advisable)

def count_up(n, counter=0):
if n > 0:
counter += 1
return count_up(n - 1, counter)
return counter

outcome = count_up(5)
print(outcome) # Output: 5

  • counter is now a parameter of the operate.
  • counter is handed from one recursion degree to the subsequent, with it’s worth up to date at every degree. The counter just isn’t reinitialised in every name, somewhat, it’s present state is handed ahead to the subsequent recursion degree.
  • The operate is now pure — there aren’t any unintended effects and it solely operates inside it’s personal scope.
  • Because the recursive operate returns, the counter “bubbles up” to the highest degree and is returned on the base case.

1. Use descriptive variable names

Keep away from obscure names like df or x. Use descriptive names similar to customer_sales_df or sales_records_df for readability.

2. Use capital letters for constants

That is the usual naming conference for constants in Python. For instance, MAX_RETRIES = 5.

3. Keep away from world variables as a lot as doable

International variables introduces bugs and makes code tougher to check and keep. It’s finest to go variables explicitly between features.

4. Purpose to put in writing pure features the place doable

What’s a pure operate?

  1. Deterministic: It at all times produces the identical output for a similar enter. It’s not affected by exterior states or randomness.
  2. Aspect-effect-free: It doesn’t modify any exterior variables or states. It operates solely inside its native scope.

Utilizing nonlocal or world would make the operate impure.

Nonetheless, when you’re working with a closure, it’s best to use the nonlocal key phrase to change variables within the enclosing (outer) scope, which helps stop variable shadowing.

A closure happens when a nested operate (inside operate) captures and refers to variables from its enclosing operate (outer operate). This permits the inside operate to “bear in mind” the setting during which it was created, together with entry to variables from the outer operate’s scope, even after the outer operate has completed executing.

The idea of closures can go actually deep, so inform me within the feedback if that is one thing I ought to dive into within the subsequent article! 🙂

5. Keep away from variable shadowing and parameter shadowing

If it’s essential discuss with an outer variable, keep away from reusing its title in an inside scope. Use distinct names to obviously distinguish the variables.

That’s a wrap! Thanks for sticking with me until the top.

Have you ever encountered any of those challenges in your personal work? Drop your ideas within the feedback under!

I write usually on Python, software program improvement and the initiatives I construct, so give me a comply with to not miss out. See you within the subsequent article 🙂