Skip to content

fix: MethodDispatcher.__get__ returns independent bound dispatcher per instance#138

Open
gaoflow wants to merge 1 commit into
mrocklin:mainfrom
gaoflow:fix/method-dispatcher-bound-instance
Open

fix: MethodDispatcher.__get__ returns independent bound dispatcher per instance#138
gaoflow wants to merge 1 commit into
mrocklin:mainfrom
gaoflow:fix/method-dispatcher-bound-instance

Conversation

@gaoflow

@gaoflow gaoflow commented Jun 24, 2026

Copy link
Copy Markdown

Bug

MethodDispatcher.__get__ stored instance and owner directly on self
(the shared descriptor object) and then returned self:

def __get__(self, instance, owner):
    self.obj = instance   # mutates the descriptor!
    self.cls = owner
    return self            # same object every time

Because every attribute lookup returns the same MethodDispatcher object,
accessing other_obj.method after storing bound = obj.method silently
overwrites self.obj, making bound(...) dispatch to the wrong instance.

Minimal reproducer:

from multipledispatch import dispatch

class Counter:
    def __init__(self):
        self.count = 0

    @dispatch(int)
    def increment(self, x):
        self.count += x

a = Counter()
b = Counter()

bound_a = a.increment
bound_b = b.increment   # overwrites bound_a.obj → a is now b!

bound_a(10)
print(a.count)  # prints 0  ← wrong, should be 10
print(b.count)  # prints 10 ← wrong, should be 0

Fix

__get__ now returns a new BoundMethodDispatcher instance that captures
its own (obj, cls) pair for each lookup, exactly as Python's ordinary
bound-method protocol works. MethodDispatcher itself is no longer
responsible for storing per-call state.

A regression test (test_method_dispatcher_bound_independence) is included.

All 63 tests pass (python -m pytest multipledispatch/tests/ -p no:randomly).


This pull request was prepared with the assistance of AI, under my direction and review.

…ispatcher

MethodDispatcher.__get__ mutated self.obj and self.cls on the descriptor
itself and returned self, so every attribute lookup shared the same object.
Accessing other_obj.method after storing bound = obj.method would silently
overwrite the stored instance, causing bound(...) to dispatch to the wrong
object.

Fix: __get__ now returns a new BoundMethodDispatcher instance that captures
the specific (instance, owner) pair for that lookup, matching the semantics
of Python's ordinary bound-method protocol.

Add test_method_dispatcher_bound_independence to cover the regression.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant