Title: Simplifying the implementation of signature-overloaded functions · Issue #7966 · matplotlib/matplotlib · GitHub
Open Graph Title: Simplifying the implementation of signature-overloaded functions · Issue #7966 · matplotlib/matplotlib
X Title: Simplifying the implementation of signature-overloaded functions · Issue #7966 · matplotlib/matplotlib
Description: There are many functions in matplotlib that have "interesting" call signatures, e.g. that could be called with 1, 2 or 3 arguments with different semantics (i.e. not just binding arguments in order and having defaults for the later ones)...
Open Graph Description: There are many functions in matplotlib that have "interesting" call signatures, e.g. that could be called with 1, 2 or 3 arguments with different semantics (i.e. not just binding arguments in order...
X Description: There are many functions in matplotlib that have "interesting" call signatures, e.g. that could be called with 1, 2 or 3 arguments with different semantics (i.e. not just binding argument...
Opengraph URL: https://github.com/matplotlib/matplotlib/issues/7966
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"Simplifying the implementation of signature-overloaded functions","articleBody":"There are many functions in matplotlib that have \"interesting\" call signatures, e.g. that could be called with 1, 2 or 3 arguments with different semantics (i.e. not just binding arguments in order and having defaults for the later ones). In this case, the binding of arguments is typically written on an ad-hoc basis, with some bugs (e.g. the one I fixed in https://github.com/matplotlib/matplotlib/pull/7859/files#diff-84224cb1c8cd1f13b7adc5930ee2fc8fR365) or difficult to read code (e.g. https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/quiver.py#L374 (`quiver._parse_args`)).\r\n\r\nMoreover, there are also cases where a function argument should be renamed, but cannot be due to backwards compatibility considerations (e.g. https://github.com/matplotlib/matplotlib/issues/7954).\r\n\r\nI propose to fix both issues using a signature-overloading decorator (see below for prototype implementation). Basically, the idea would be to write something like\r\n```\r\n@signature_dispatch\r\ndef func(\u003cinner_signature\u003e): ... # inner function definition\r\n\r\n@func.overload\r\ndef func(\u003csignature_1\u003e):\r\n # \u003cplay with args until they match inner_signature\u003e\r\n return func.__wrapped__(\u003cnew_args\u003e) # Refers to the \"inner\" function\r\n\r\n@func.overload\r\ndef func(\u003csignature_2\u003e):\r\n # \u003cplay with args until they match inner_signature\u003e\r\n return func.__wrapped__(\u003cnew_args\u003e) # Refers to the \"inner\" function\r\n```\r\nwhere the first overload that can bind the arguments is the one used.\r\n\r\nIn order to support changes in signature due to argument renaming, an overload with the previous signature that raises a DeprecationWarning before forwarding the argument to the \"inner\" function can be used.\r\n\r\nThoughts?\r\n\r\nProtoype implementation:\r\n\r\n```\r\n\"\"\"A signature dispatch decorator.\r\n\r\nDecorate a function using::\r\n\r\n @signature_dispatch\r\n def func(...):\r\n ...\r\n\r\nand provide signature overloads using::\r\n\r\n @func.overload\r\n def func(...): # Note the use of the same name.\r\n ...\r\n # Refer to the \"original\" function as ``func.__wrapped__``\r\n\r\nCalling the function will try binding the arguments passed to each overload in\r\nturn, until one binding succeeds; that overload will be called and its return\r\nvalue (or raised Exception) be used for the original function call.\r\n\r\nOverloads can define keyword-only arguments in trailing position in a Py2\r\ncompatible manner by having a marker argument named ``__kw_only__``; that\r\nargument behaves like \"*\" in Py3, i.e., later arguments become keyword-only\r\n(and the ``__kw_only__`` argument itself is always bound to None).\r\n\r\nOverloads can define positional arguments in leading position (as defined in\r\nhttps://docs.python.org/3/library/inspect.html#inspect.Parameter.kind) by\r\nhaving a marker argument named ``__pos_only__``; earlier arguments become\r\npositional-only (and the ``__pos_only__`` argument iself is always bound to\r\nNone).\r\n\r\nThis implementation should be compatible with Py2 as long as a backport of the\r\nsignature object (e.g. funcsigs) is used instead.\r\n\"\"\"\r\n\r\n\r\nfrom collections import OrderedDict\r\nfrom functools import wraps\r\nfrom inspect import signature\r\n\r\n\r\ndef signature_dispatch(func):\r\n\r\n def overload(impl):\r\n sig = signature(impl)\r\n params = list(sig.parameters.values())\r\n try:\r\n idx = next(idx for idx, param in enumerate(params)\r\n if param.name == \"__pos_only__\")\r\n except ValueError:\r\n pass\r\n else:\r\n # Make earlier parameters positional only, skip __pos_only__ marker.\r\n params = ([param.replace(kind=param.POSITIONAL_ONLY)\r\n for param in params[:idx]]\r\n + params[idx + 1:])\r\n try:\r\n idx = next(idx for idx, param in enumerate(params)\r\n if param.name == \"__kw_only__\")\r\n except ValueError:\r\n pass\r\n else:\r\n # Make later parameters positional only, skip __kw_only__ marker.\r\n params = (params[:idx]\r\n + [param.replace(kind=param.KEYWORD_ONLY)\r\n for param in params[idx + 1:]]\r\n\r\n sig = sig.replace(parameters=params)\r\n impls_sigs.append((impl, sig))\r\n return wrapper\r\n\r\n @wraps(func)\r\n def wrapper(*args, **kwargs):\r\n for impl, sig in impls_sigs:\r\n try:\r\n ba = sig.bind(*args, **kwargs)\r\n except TypeError:\r\n continue\r\n else:\r\n if \"__pos_only__\" in signature(impl).parameters:\r\n ba.arguments[\"__pos_only__\"] = None\r\n return impl(**ba.arguments)\r\n raise TypeError(\"No matching signature\")\r\n\r\n impls_sigs = []\r\n wrapper.overload = overload\r\n return wrapper\r\n\r\n\r\n@signature_dispatch\r\ndef slice_like(x, y, z):\r\n return slice(x, y, z)\r\n\r\n@slice_like.overload\r\ndef slice_like(x, __pos_only__):\r\n return slice_like.__wrapped__(None, x, None)\r\n\r\n@slice_like.overload\r\ndef slice_like(x, y, __pos_only__):\r\n return slice_like.__wrapped__(x, y, None)\r\n\r\n@slice_like.overload\r\ndef slice_like(x, y, z, __pos_only__):\r\n return slice_like.__wrapped__(x, y, z)\r\n\r\nassert slice_like(10) == slice(10)\r\nassert slice_like(10, 20) == slice(10, 20)\r\nassert slice_like(10, 20, 30) == slice(10, 20, 30)\r\ntry: slice_like(x=10)\r\nexcept TypeError: pass\r\nelse: assert False\r\n```","author":{"url":"https://github.com/anntzer","@type":"Person","name":"anntzer"},"datePublished":"2017-01-28T01:11:18.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":5},"url":"https://github.com/7966/matplotlib/issues/7966"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:2e9e922b-b5b2-891a-8002-970cb3e9b3e6 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | BA40:1B675D:29040D3:37EFF30:6A53BD6A |
| html-safe-nonce | a7136eef69c248f61f161a4cc4033c4250ed97e07d2eb6585ee48500da45d0c1 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJCQTQwOjFCNjc1RDoyOTA0MEQzOjM3RUZGMzA6NkE1M0JENkEiLCJ2aXNpdG9yX2lkIjoiNjgwOTU4ODA1MzA3NTgwMzQ5OSIsInJlZ2lvbl9lZGdlIjoiaWFkIiwicmVnaW9uX3JlbmRlciI6ImlhZCJ9 |
| visitor-hmac | d2a83d8c1a5c5613e998a85b146e2836e2a0bd936a6e112757aaab5d2783936f |
| hovercard-subject-tag | issue:203776892 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/matplotlib/matplotlib/7966/issue_layout |
| twitter:image | https://opengraph.githubassets.com/bc1e72e4235a7187d58578814c1e622690f473aa43a936072bedc85499322ec6/matplotlib/matplotlib/issues/7966 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/bc1e72e4235a7187d58578814c1e622690f473aa43a936072bedc85499322ec6/matplotlib/matplotlib/issues/7966 |
| og:image:alt | There are many functions in matplotlib that have "interesting" call signatures, e.g. that could be called with 1, 2 or 3 arguments with different semantics (i.e. not just binding arguments in order... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | anntzer |
| hostname | github.com |
| expected-hostname | github.com |
| None | b9a586c06a05a7a86fc7e3f4dbd03e42f6869085879aa184aa6369456dbd50fb |
| turbo-cache-control | no-preview |
| go-import | github.com/matplotlib/matplotlib git https://github.com/matplotlib/matplotlib.git |
| octolytics-dimension-user_id | 215947 |
| octolytics-dimension-user_login | matplotlib |
| octolytics-dimension-repository_id | 1385122 |
| octolytics-dimension-repository_nwo | matplotlib/matplotlib |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 1385122 |
| octolytics-dimension-repository_network_root_nwo | matplotlib/matplotlib |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 07a982c1d40157c619b364352b704c3ce66bb332 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width