Alright, let’s talk about one of the most common, and frankly, easiest-to-fix performance drains I see in the wild: the double-whammy of global lookups and repeated attribute access. This isn’t about fancy algorithmic wizardry; this is about cleaning up the sloppy, lazy code we all write at 2 AM so it doesn’t embarrass us in the light of day.

Think of your code’s namespace as a series of concentric circles. The innermost circle is your local scope. It’s the VIP lounge—getting in there is fast, cheap, and easy. The outermost circle is the global scope, which includes built-in names like len or str. That’s the parking lot. Every time you reference a name in that global scope, the interpreter has to leave the comfy VIP lounge, trudge out to the parking lot, and yell for it. This process—a global lookup—isn’t cripplingly slow, but do it enough times inside a tight loop and it starts to add up like a bar tab at a developer conference.

The Cost of a Round Trip to Global

Let’s make this concrete. Imagine you’re summing a list of numbers. You might write:

import math

def calculate_sums(numbers):
    results = []
    for num in numbers:
        # Two global lookups per iteration: 'math' and 'math.sqrt'
        results.append(math.sqrt(num) + math.log(num))
    return results

Seems fine, right? But for every single number in that list, Python is doing this: “Okay, math… where’s math? searches global scope Ah, there you are. Now, what’s your sqrt attribute? searches the math module’s namespace Got it.” It does this twice per loop. We’re sending the poor interpreter on two completely avoidable errands millions of times.

Localize Your Dependencies

The fix is embarrassingly simple: bring the stuff inside. Assign the functions you need to a local variable before the loop. Local variable lookups are blazingly fast because they’re stored in an array indexed by integer, not a dictionary that requires hashing a name.

import math

def calculate_sums_optimized(numbers):
    results = []
    # One-time global lookup, assigned to local vars
    local_sqrt = math.sqrt
    local_log = math.log
    for num in numbers:
        # Now only fast local lookups
        results.append(local_sqrt(num) + local_log(num))
    return results

This isn’t a micro-optimization; it’s just writing sensible code. You’ve eliminated millions of lookups. The same principle applies to methods on objects. Don’t do this:

for item in my_gigantic_list:
    processed = item.strip().lower().split()  # Three attribute lookups per item!

If you’re dealing with a list of strings, item.strip is still an attribute lookup on each item. In this case, it’s probably unavoidable. But if you’re calling a method on the same object repeatedly, you’re committing a cardinal sin.

The Self-inflicted Wound: Repeated Attribute Access

This one makes me sigh. It’s the programming equivalent of running back into your house because you forgot your keys three separate times.

# Painful to watch
for i in range(len(very_long_list)):
    if very_long_list[i].is_special:  # 1. Look up 'very_long_list', 2. index it, 3. lookup 'is_special'
        very_long_list[i].process()   # 4. Do it all again, 5. then lookup 'process'
        print(very_long_list[i].name) # 6. I'm not even kidding, you did it a third time.

Stop. You found the object. Hold on to it.

# Much, much better
for item in very_long_list:  # Just get the item once
    if item.is_special:      # One attribute lookup
        item.process()       # One attribute lookup
        print(item.name)     # One attribute lookup

We’ve gone from six operations per object down to three. This pattern is even more critical with complex data structures. Never write my_dict['a']['b']['c'] in a loop. Unpack it step-by-step into local variables.

When It Doesn’t Matter (And When It Absolutely Does)

Now, let’s be real. If you’re doing this lookup once outside a loop, the cost is negligible. Don’t obfuscate a simple os.path.join call because you’re afraid of a global lookup. The problem is repetition.

The hot loop is where you pay the price. The inner core of your algorithm, the function called millions of times—that’s where you need to be ruthless. Profile your code (we’ll get to that later, because you should always profile before optimizing) and you’ll instantly see these unnecessary lookups light up like a Christmas tree.

The best part? This optimization doesn’t change your code’s behavior one bit. It just makes it faster. It’s a free lunch, and you’d be a fool not to take it.