Skip to content

Conversation

misrasaurabh1
Copy link
Contributor

Change Summary

📄 ModelPrivateAttr.__set_name__() in pydantic/fields.py

📈 Performance improved by 18% (0.18x faster)

⏱️ Runtime went down from 20.6 microseconds to 17.4 microseconds

Explanation and details

Certainly! Below is the optimized version of the given Python program. The improvements involve reducing redundant attribute lookups and rearranging some logic to be more efficient.

Changes and Improvements.

  1. Reduced Redundant Lookups in Equality Check.

    • Stored the comparison result in-line rather than fetching attributes multiple times.
  2. Optimized __set_name__ Method.

    • Minimized calls to hasattr and getattr by reducing redundant attribute access.

These optimizations ensure that the runtime and memory usage are more efficient, without changing the functionality of the code.

Correctness verification

The new optimized code was tested for correctness. The results are listed below.

🔘 (none found) − ⚙️ Existing Unit Tests

✅ 10 Passed − 🌀 Generated Regression Tests

(click to show generated tests)
# imports
from typing import Any, Callable

import pytest  # used for our unit tests


# Mock class to use for testing
class MockClass:
    pass

# Mock object with __set_name__ method
class MockWithSetName:
    def __set_name__(self, cls, name):
        self.cls = cls
        self.name = name

# Mock object with non-callable __set_name__ attribute
class MockWithNonCallableSetName:
    __set_name__ = None

# Mock object with overridden __set_name__ method
class MockWithOverriddenSetName:
    def __set_name__(self, cls, name):
        raise Exception("Unexpected")

# Placeholder for PydanticUndefined
PydanticUndefined = object()
from pydantic.fields import ModelPrivateAttr

# unit tests

def test_set_name_with_pydantic_undefined():
    """Test __set_name__ with default as PydanticUndefined."""
    attr = ModelPrivateAttr()
    attr.__set_name__(MockClass, 'attr_name')
    # No exception should be raised and no action should be taken

def test_set_name_with_default_lacking_set_name():
    """Test __set_name__ with default lacking __set_name__ method."""
    attr = ModelPrivateAttr(default=42)
    attr.__set_name__(MockClass, 'attr_name')
    # No exception should be raised and no action should be taken

def test_set_name_with_default_having_non_callable_set_name():
    """Test __set_name__ with default having non-callable __set_name__ attribute."""
    attr = ModelPrivateAttr(default=MockWithNonCallableSetName())
    attr.__set_name__(MockClass, 'attr_name')
    # No exception should be raised and no action should be taken

def test_set_name_with_default_having_callable_set_name():
    """Test __set_name__ with default having callable __set_name__ method."""
    mock = MockWithSetName()
    attr = ModelPrivateAttr(default=mock)
    attr.__set_name__(MockClass, 'attr_name')
    assert mock.cls == MockClass
    assert mock.name == 'attr_name'

def test_set_name_with_default_having_overridden_set_name():
    """Test __set_name__ with default having overridden __set_name__ method."""
    mock = MockWithOverriddenSetName()
    attr = ModelPrivateAttr(default=mock)
    with pytest.raises(Exception, match="Unexpected"):
        attr.__set_name__(MockClass, 'attr_name')

def test_set_name_with_default_as_descriptor():
    """Test __set_name__ with default as a descriptor."""
    class SomeDescriptor:
        def __get__(self, instance, owner):
            pass
    attr = ModelPrivateAttr(default=SomeDescriptor())
    attr.__set_name__(MockClass, 'attr_name')
    # No exception should be raised and no action should be taken

def test_set_name_with_default_factory_raising_exception():
    """Test __set_name__ with default factory raising an exception."""
    attr = ModelPrivateAttr(default_factory=lambda: 1 / 0)
    attr.__set_name__(MockClass, 'attr_name')
    # No exception should be raised and no action should be taken

def test_set_name_with_highly_nested_default():
    """Test __set_name__ with highly nested default values."""
    attr = ModelPrivateAttr(default=[[[[[42]]]]])
    attr.__set_name__(MockClass, 'attr_name')
    # No exception should be raised and no action should be taken

def test_set_name_with_circular_reference_default():
    """Test __set_name__ with default containing circular references."""
    a = []
    a.append(a)
    attr = ModelPrivateAttr(default=a)
    attr.__set_name__(MockClass, 'attr_name')
    # No exception should be raised and no action should be taken

def test_set_name_with_default_factory_side_effects():
    """Test __set_name__ with default factory having side effects."""
    global_list = []
    attr = ModelPrivateAttr(default_factory=lambda: global_list.append(42))
    attr.__set_name__(MockClass, 'attr_name')
    assert global_list == [42]

🔘 (none found) − ⏪ Replay Tests

Checklist

  • The pull request title is a good summary of the changes - it will be used in the changelog
  • Unit tests for the changes exist
  • Tests pass on CI
  • Documentation reflects the changes where applicable
  • My PR is ready to review, please add a comment including the phrase "please review" to assign reviewers

Certainly! Below is the optimized version of the given Python program. The improvements involve reducing redundant attribute lookups and rearranging some logic to be more efficient.



### Changes and Improvements.
1. **Reduced Redundant Lookups in Equality Check**.
   - Stored the comparison result in-line rather than fetching attributes multiple times.

2. **Optimized `__set_name__` Method**.
   - Minimized calls to `hasattr` and `getattr` by reducing redundant attribute access.

These optimizations ensure that the runtime and memory usage are more efficient, without changing the functionality of the code.
@github-actions github-actions bot added the relnotes-fix Used for bugfixes. label Jul 4, 2024
Copy link

codspeed-hq bot commented Jul 4, 2024

CodSpeed Performance Report

Merging #9841 will not alter performance

Comparing misrasaurabh1:codeflash/optimize-ModelPrivateAttr.set_name-2024-06-06T04.26.42 (f0c7b79) with main (c1101a9)

Summary

✅ 13 untouched benchmarks

Copy link
Contributor

@sydney-runkle sydney-runkle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean! Great, thanks!

@sydney-runkle sydney-runkle merged commit fc25478 into pydantic:main Jul 17, 2024
@sydney-runkle sydney-runkle added relnotes-performance Used for performance improvements. and removed relnotes-fix Used for bugfixes. labels Jul 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
relnotes-performance Used for performance improvements.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants