Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Support unhashable callbacks in CallbackRegistry#26013

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
ksunden merged 3 commits intomatplotlib:mainfromanntzer:cbr
Sep 19, 2024

Conversation

anntzer
Copy link
Contributor

@anntzeranntzer commentedMay 31, 2023
edited
Loading

PR summary

Closes#26012.

The first two commits are cleanup-ish:

  • Record the connected signal in CallbackRegistry weakref cleanup function, to remove the need to loop over all signals in _remove_proxy.
  • Flatten CallbackRegistry._func_cid_map: It is easier to manipulate a flat (signal, proxy) -> cid map rather than
    a nested signal -> (proxy -> cid) map (especially for the third commit).

The last commit implements support for unhashable callbacks, by replacing _func_cid_map by a dict-like structure (_UnhashDict) that also supports unhashable entries.
Note that _func_cid_map (and thus _UnhashDict) can be dropped if we get rid of proxy deduplication in CallbackRegistry (#20210).


edit: ah, the joys of type-checking 😑 (python/mypy#4266)

PR checklist

tacaswell
tacaswell previously approved these changesJun 1, 2023
Copy link
Member

@tacaswelltacaswell left a comment

Choose a reason for hiding this comment

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

I am not convinced this is a good idea, but looks technically correct.

…ion.... to remove the need to loop over all signals in _remove_proxy.
It is easier to manipulate a flat (signal, proxy) -> cid map rather thana nested signal -> (proxy -> cid) map.
... by replacing _func_cid_map by a dict-like structure (_UnhashDict)that also supports unhashable entries.Note that _func_cid_map (and thus _UnhashDict) can be dropped if we getrid of proxy deduplication in CallbackRegistry.
@tacaswell
Copy link
Member

After discussion with@ksunden , we think it is simpler to fully removeself._func_cid_map and pay the cost of a linear search overself.callbacks when we need to rather than to have an very weird dictionary relative.

@tacaswelltacaswell dismissed theirstale reviewDecember 20, 2023 22:17

We think there is a simpler way.

@anntzer
Copy link
ContributorAuthor

Did you consider#20210 as well?

@tacaswell
Copy link
Member

We would still need the linear search in the_remove_proxy callback on the weak/strong ref.

@anntzer
Copy link
ContributorAuthor

Not if you also record the proxy's cid in the weakref callback, similarly to how I record the signal name atb629ff8#diff-84a45e2dda93ad4c1d950da5c16846965aa8d96aebfe8d00acaeb83a18c1b2f0R215? (Fortunately, per the weakref docs: "If the referents are still alive, two references have the same equality relationship as their referents (regardless of the callback".)

@tacaswell
Copy link
Member

you are correct. I went through a couple of versions of injecting the cid that did not work but did not make it topartial.....

@anntzer
Copy link
ContributorAuthor

The patch is basically

diff --git i/lib/matplotlib/cbook.py w/lib/matplotlib/cbook.pyindex 4d6dc04a9b..7c1b0ef246 100644--- i/lib/matplotlib/cbook.py+++ w/lib/matplotlib/cbook.py@@ -107,7 +107,7 @@ class _StrongRef:         return hash(self._obj)-def _weak_or_strong_ref(func, callback):+def _weak_or_strong_ref(func, callback=None):     """     Return a `WeakMethod` wrapping *func* if possible, else a `_StrongRef`.     """@@ -204,7 +204,8 @@ class CallbackRegistry:         cid_count = state.pop('_cid_gen')         vars(self).update(state)         self.callbacks = {-            s: {cid: _weak_or_strong_ref(func, functools.partial(self._remove_proxy, s))+            s: {cid: _weak_or_strong_ref(func,+                                         functools.partial(self._remove_proxy, s, cid))                 for cid, func in d.items()}             for s, d in self.callbacks.items()}         self._func_cid_map = {@@ -217,10 +218,15 @@ class CallbackRegistry:         if self._signals is not None:             _api.check_in_list(self._signals, signal=signal)         self._func_cid_map.setdefault(signal, {})-        proxy = _weak_or_strong_ref(func, functools.partial(self._remove_proxy, signal))+        # First check whether we're actually going to add the callback.  This+        # relies on weakref comparison not taking the weakref destruction+        # callback (which includes the cid here) into account.+        proxy = _weak_or_strong_ref(func)         if proxy in self._func_cid_map[signal]:             return self._func_cid_map[signal][proxy]         cid = next(self._cid_gen)+        proxy = _weak_or_strong_ref(func,+                                    functools.partial(self._remove_proxy, signal, cid))         self._func_cid_map[signal][proxy] = cid         self.callbacks.setdefault(signal, {})         self.callbacks[signal][cid] = proxy@@ -238,16 +244,14 @@ class CallbackRegistry:      # Keep a reference to sys.is_finalizing, as sys may have been cleared out     # at that point.-    def _remove_proxy(self, signal, proxy, *, _is_finalizing=sys.is_finalizing):+    def _remove_proxy(self, signal, cid, proxy, *, _is_finalizing=sys.is_finalizing):         if _is_finalizing():             # Weakrefs can't be properly torn down at that point anymore.             return-        cid = self._func_cid_map[signal].pop(proxy, None)-        if cid is not None:-            del self.callbacks[signal][cid]-            self._pickled_cids.discard(cid)-        else:  # Not found-            return+        if self._func_cid_map[signal].pop(proxy, None) is None:+            return  # Not found.+        del self.callbacks[signal][cid]+        self._pickled_cids.discard(cid)         # Clean up empty dicts         if len(self.callbacks[signal]) == 0:             del self.callbacks[signal]

where the need for pickling is really restricted only to _func_cid_map (i.e. deduplication) now.

Even if we really want to support an option for deduplication (which I would say should not be the default) per#20210 (comment), we could just fall back to linear search for that case only.

@tacaswell
Copy link
Member

Coming back to this I think we do want to add an option to deduplicate or not defaulting to not (and a version where if not explicitly told we do deduplicate but warn we won't in the future). As part of that we should move to paying for a linear search and not pick up a dictionary cousin.

@tacaswelltacaswell modified the milestones:v3.9.0,v3.10.0Mar 6, 2024
@anntzer
Copy link
ContributorAuthor

@tacaswell perhaps I'll close this and let you take it over?

Copy link
Member

@tacaswelltacaswell left a comment

Choose a reason for hiding this comment

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

Following up, no further work has materialized, this adds some complexity, but does not break any existing behavior and fixes a thing that would be reasonable that it worked.

We can leave the de-duplication work for later (and punt simplifying it to then).

@tacaswell
Copy link
Member

power-cycled to re-run the tests.

@ksundenksunden merged commit9675122 intomatplotlib:mainSep 19, 2024
38 of 41 checks passed
@anntzeranntzer deleted the cbr branchSeptember 19, 2024 19:59
kyracho pushed a commit to kyracho/matplotlib that referenced this pull requestOct 10, 2024
* Record the connected signal in CallbackRegistry weakref cleanup function.... to remove the need to loop over all signals in _remove_proxy.* Flatten CallbackRegistry._func_cid_map.It is easier to manipulate a flat (signal, proxy) -> cid map rather thana nested signal -> (proxy -> cid) map.* Support unhashable callbacks in CallbackRegistry.... by replacing _func_cid_map by a dict-like structure (_UnhashDict)that also supports unhashable entries.Note that _func_cid_map (and thus _UnhashDict) can be dropped if we getrid of proxy deduplication in CallbackRegistry.
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@tacaswelltacaswelltacaswell approved these changes

Assignees
No one assigned
Labels
None yet
Projects
None yet
Milestone
v3.10.0
Development

Successfully merging this pull request may close these issues.

[Bug]: "Unhashable type" when event callback is a method of adict subclass
3 participants
@anntzer@tacaswell@ksunden

[8]ページ先頭

©2009-2025 Movatter.jp