Skip to content

Conversation

codeflash-ai[bot]
Copy link

@codeflash-ai codeflash-ai bot commented Oct 7, 2025

📄 7% (0.07x) speedup for __getattr__ in quantecon/filter.py

⏱️ Runtime : 677 microseconds 635 microseconds (best of 178 runs)

📝 Explanation and details

The optimization introduces message caching to eliminate redundant string formatting operations in the deprecation warning system.

Key changes:

  • Added _warn_msg_cache dictionary to store pre-formatted warning messages
  • Replaced inline f-string construction with cached lookup using _warn_msg_cache.get(name)
  • Warning messages are only constructed once per unique attribute name, then reused

Why this improves performance:
The original code performed expensive f-string formatting on every __getattr__ call (line taking 77.8% of execution time). The optimization moves this cost to first-access only - subsequent calls for the same attribute name retrieve the pre-formatted message from the cache in O(1) time.

Performance characteristics:

  • First access: Slight overhead due to cache miss and storage (~same speed)
  • Repeated access: Significant speedup by avoiding string formatting
  • Test results show 6-10% improvements for repeated calls to the same attribute, with the largest gains in test_getattr_performance_many_calls (6.79% faster) where the same attribute is accessed 500 times

This optimization is most effective for codebases that trigger the same deprecated attribute warnings multiple times, which is common in real-world usage patterns where deprecated APIs are called repeatedly.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 525 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 80.0%
🌀 Generated Regression Tests and Runtime
import types
import warnings

# imports
import pytest  # used for our unit tests
from quantecon.filter import __getattr__


def dummy_hamilton_filter():
    """Dummy function to represent hamilton_filter."""
    return "hamilton_filter_called"

# Create a dummy _filter module with hamilton_filter attribute
_filter = types.SimpleNamespace(hamilton_filter=dummy_hamilton_filter)

__all__ = ['hamilton_filter']

def __dir__():
    return __all__
from quantecon.filter import __getattr__  # --- End of testable code block ---

# unit tests

# 1. Basic Test Cases

def test_getattr_returns_hamilton_filter():
    """
    Test that __getattr__ returns the correct attribute when given a valid name.
    """
    # Should return the dummy_hamilton_filter function
    codeflash_output = __getattr__('hamilton_filter'); attr = codeflash_output # 5.06μs -> 4.71μs (7.52% faster)

def test_getattr_warns_deprecated(monkeypatch):
    """
    Test that __getattr__ issues a DeprecationWarning when called with a valid attribute.
    """
    # Capture warnings
    with warnings.catch_warnings(record=True) as w:
        warnings.simplefilter("always")
        codeflash_output = __getattr__('hamilton_filter'); result = codeflash_output # 3.98μs -> 3.73μs (6.82% faster)
        warning = w[0]

def test_dir_returns_all():
    """
    Test that __dir__ returns the correct __all__ list.
    """

# 2. Edge Test Cases

def test_getattr_invalid_attribute_raises():
    """
    Test that __getattr__ raises AttributeError for an invalid attribute.
    """
    with pytest.raises(AttributeError) as excinfo:
        __getattr__('not_a_real_attr') # 1.12μs -> 1.14μs (1.67% slower)
    # The error message should mention the missing attribute and deprecation
    msg = str(excinfo.value)

def test_getattr_empty_string_raises():
    """
    Test that __getattr__ raises AttributeError when given an empty string.
    """
    with pytest.raises(AttributeError) as excinfo:
        __getattr__('') # 986ns -> 980ns (0.612% faster)
    msg = str(excinfo.value)

def test_getattr_case_sensitivity():
    """
    Test that __getattr__ is case-sensitive (should raise for wrong case).
    """
    with pytest.raises(AttributeError):
        __getattr__('Hamilton_Filter') # 890ns -> 896ns (0.670% slower)
    with pytest.raises(AttributeError):
        __getattr__('HAMILTON_FILTER') # 563ns -> 599ns (6.01% slower)


def test_getattr_with_special_characters():
    """
    Test that __getattr__ raises AttributeError for special character attribute names.
    """
    with pytest.raises(AttributeError):
        __getattr__('hamilton-filter') # 1.10μs -> 1.13μs (2.57% slower)
    with pytest.raises(AttributeError):
        __getattr__('@hamilton_filter') # 696ns -> 722ns (3.60% slower)
    with pytest.raises(AttributeError):
        __getattr__('hamilton filter') # 465ns -> 499ns (6.81% slower)

def test_getattr_does_not_warn_on_invalid(monkeypatch):
    """
    Test that __getattr__ does not issue a warning when raising AttributeError.
    """
    with warnings.catch_warnings(record=True) as w:
        warnings.simplefilter("always")
        with pytest.raises(AttributeError):
            __getattr__('not_in_all')

# 3. Large Scale Test Cases

def test_getattr_performance_many_calls():
    """
    Test that repeated calls to __getattr__ are consistent and performant.
    """
    # Call __getattr__ 500 times with the valid name and check all results
    for _ in range(500):
        codeflash_output = __getattr__('hamilton_filter'); attr = codeflash_output # 643μs -> 602μs (6.79% faster)


def test_getattr_large_invalid(monkeypatch):
    """
    Test __getattr__ with a large __all__ and invalid attribute.
    """
    # Create a large __all__ and _filter with 1000 attributes
    large_all = [f"func{i}" for i in range(1000)]
    dummy_funcs = {name: (lambda n=name: f"called_{n}") for name in large_all}
    large_filter = types.SimpleNamespace(**dummy_funcs)

    # Patch __all__ and _filter for this test only
    monkeypatch.setitem(globals(), "__all__", large_all)
    monkeypatch.setitem(globals(), "_filter", large_filter)

    # Try to access an attribute not in __all__
    with pytest.raises(AttributeError) as excinfo:
        __getattr__('not_present') # 1.35μs -> 1.36μs (0.881% slower)
    msg = str(excinfo.value)

def test_getattr_large_dir(monkeypatch):
    """
    Test __dir__ returns the large __all__ list.
    """
    large_all = [f"func{i}" for i in range(1000)]
    monkeypatch.setitem(globals(), "__all__", large_all)
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.
#------------------------------------------------
import sys
import types  # used to create dummy modules for testing
import warnings

# imports
import pytest  # used for our unit tests
from quantecon import _filter
from quantecon.filter import __getattr__

__all__ = ['hamilton_filter']

def __dir__():
    return __all__
from quantecon.filter import __getattr__

# unit tests

# ----------- BASIC TEST CASES -----------

def test_basic_valid_attribute_returns_function():
    """
    Test that __getattr__ returns the correct attribute when called with a valid name.
    """
    codeflash_output = __getattr__('hamilton_filter'); attr = codeflash_output # 4.46μs -> 4.03μs (10.6% faster)

def test_basic_valid_attribute_warns_deprecation():
    """
    Test that __getattr__ emits a DeprecationWarning when called with a valid name.
    """
    with warnings.catch_warnings(record=True) as w:
        warnings.simplefilter("always")
        __getattr__('hamilton_filter') # 3.85μs -> 3.56μs (8.15% faster)

def test_basic_invalid_attribute_raises_attributeerror():
    """
    Test that __getattr__ raises AttributeError for an invalid attribute name.
    """
    with pytest.raises(AttributeError) as excinfo:
        __getattr__('not_in_all') # 1.04μs -> 1.02μs (1.66% faster)

# ----------- EDGE TEST CASES -----------

def test_edge_empty_string_attribute():
    """
    Test that __getattr__ raises AttributeError for empty string attribute.
    """
    with pytest.raises(AttributeError):
        __getattr__('') # 971ns -> 937ns (3.63% faster)



def test_edge_attribute_case_sensitivity():
    """
    Test that __getattr__ is case-sensitive (should not find 'Hamilton_Filter').
    """
    with pytest.raises(AttributeError):
        __getattr__('Hamilton_Filter') # 1.14μs -> 1.12μs (1.87% faster)

def test_edge_attribute_with_whitespace():
    """
    Test that __getattr__ does not strip whitespace and raises AttributeError.
    """
    with pytest.raises(AttributeError):
        __getattr__(' hamilton_filter ') # 979ns -> 951ns (2.94% faster)
    with pytest.raises(AttributeError):
        __getattr__('hamilton_filter ') # 622ns -> 593ns (4.89% faster)

def test_edge_attribute_with_special_characters():
    """
    Test that __getattr__ raises AttributeError for attributes with special characters.
    """
    with pytest.raises(AttributeError):
        __getattr__('hamilton_filter!') # 827ns -> 803ns (2.99% faster)
    with pytest.raises(AttributeError):
        __getattr__('hamilton@filter') # 660ns -> 720ns (8.33% slower)

def test_edge_attribute_in_all_but_missing_in_filter(monkeypatch):
    """
    Test that __getattr__ raises AttributeError if attribute is in __all__ but not in _filter.
    """
    # Add a new attribute to __all__ that _filter does not have
    monkeypatch.setitem(globals(), '__all__', ['hamilton_filter', 'missing_attr'])
    with pytest.raises(AttributeError):
        __getattr__('missing_attr') # 868ns -> 823ns (5.47% faster)
    # Restore __all__
    monkeypatch.setitem(globals(), '__all__', ['hamilton_filter'])

# ----------- LARGE SCALE TEST CASES -----------


def test_large_scale_invalid_attribute_in_large_all(monkeypatch):
    """
    Test that __getattr__ raises AttributeError for an invalid attribute in a large __all__.
    """
    monkeypatch.setitem(globals(), '__all__', [f'attr_{i}' for i in range(1000)])
    with pytest.raises(AttributeError):
        __getattr__('not_a_real_attr') # 1.28μs -> 1.25μs (1.99% faster)
    # Restore __all__
    monkeypatch.setitem(globals(), '__all__', ['hamilton_filter'])


def test_misc_dir_returns_all():
    """
    Test that __dir__ returns __all__.
    """
# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.

To edit these changes git checkout codeflash/optimize-__getattr__-mggty2bv and push.

Codeflash

The optimization introduces **message caching** to eliminate redundant string formatting operations in the deprecation warning system. 

**Key changes:**
- Added `_warn_msg_cache` dictionary to store pre-formatted warning messages
- Replaced inline f-string construction with cached lookup using `_warn_msg_cache.get(name)`
- Warning messages are only constructed once per unique attribute name, then reused

**Why this improves performance:**
The original code performed expensive f-string formatting on every `__getattr__` call (line taking 77.8% of execution time). The optimization moves this cost to first-access only - subsequent calls for the same attribute name retrieve the pre-formatted message from the cache in O(1) time.

**Performance characteristics:**
- **First access**: Slight overhead due to cache miss and storage (~same speed)
- **Repeated access**: Significant speedup by avoiding string formatting
- **Test results show 6-10% improvements** for repeated calls to the same attribute, with the largest gains in `test_getattr_performance_many_calls` (6.79% faster) where the same attribute is accessed 500 times

This optimization is most effective for codebases that trigger the same deprecated attribute warnings multiple times, which is common in real-world usage patterns where deprecated APIs are called repeatedly.
@codeflash-ai codeflash-ai bot requested a review from mashraf-222 October 7, 2025 17:24
@codeflash-ai codeflash-ai bot added the ⚡️ codeflash Optimization PR opened by Codeflash AI label Oct 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚡️ codeflash Optimization PR opened by Codeflash AI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants