Skip to content

Commit

Permalink
fix: handling of operator.{attrgetter,itemgetter,methodcaller} instan…
Browse files Browse the repository at this point in the history
…ces as callbacks due to their inspect signatures reporting that they are able to handle more call arguments than they actually can

Fixes #225
  • Loading branch information
dgilland committed Apr 26, 2024
1 parent dc63461 commit 0358c81
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 8 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
Changelog
=========

- Fix issue where too many arguments were passed to stdlib's ``operator.attrgetter``, ``operator.itemgetter``, and ``operator.methodcaller`` when instances of those classes were used as callbacks to functions like ``map_``, ``filter_``, etc. due to a bug introduced in Python 3.12.3 and 3.11.9 that reported an incorrect signature for those ``operator`` class instances.


v8.0.0 (2024-03-26)
-------------------

Expand Down
20 changes: 13 additions & 7 deletions src/pydash/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from decimal import Decimal
from functools import wraps
import inspect
from inspect import getfullargspec
import operator
import warnings

import pydash as pyd
Expand Down Expand Up @@ -72,19 +72,25 @@ def _getargcount(iteratee, maxargs):
# getting the iteratee argcount since it takes into account the "self" argument in callable
# classes.
sig = inspect.signature(iteratee)
except (TypeError, ValueError, AttributeError):
except (TypeError, ValueError, AttributeError): # pragma: no cover
pass
else: # pragma: no cover
else:
if not any(
param.kind == inspect.Parameter.VAR_POSITIONAL for param in sig.parameters.values()
):
argcount = len(sig.parameters)

if argcount is None:
argspec = getfullargspec(iteratee)
if argspec and not argspec.varargs: # pragma: no cover
# Use inspected arg count.
argcount = len(argspec.args)
# Signatures were added these operator methods in Python 3.12.3 and 3.11.9 but their
# instance objects are incorrectly reported as accepting varargs when they only accept a
# single argument.
if isinstance(iteratee, (operator.itemgetter, operator.attrgetter, operator.methodcaller)):
argcount = 1
else:
argspec = inspect.getfullargspec(iteratee)
if argspec and not argspec.varargs: # pragma: no cover
# Use inspected arg count.
argcount = len(argspec.args)

if argcount is None:
# Assume all args are handleable.
Expand Down
18 changes: 17 additions & 1 deletion tests/test_collections.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from collections import namedtuple
import math
from operator import itemgetter
from operator import attrgetter, itemgetter, methodcaller

import pytest

Expand Down Expand Up @@ -323,6 +323,22 @@ def test_key_by(case, expected):
[(1, 2), (3, 4), (5, 6)],
False,
),
(
(
[helpers.Object(a=1, b=2, c=-1), helpers.Object(a=3, b=4, c=-1)],
attrgetter("a", "b"),
),
[(1, 2), (3, 4)],
False,
),
(
(
[{"a": 1, "b": 2, "c": -1}, {"a": 3, "b": 4}, {"a": 5}],
methodcaller("__len__"),
),
[3, 2, 1],
False,
),
],
)
def test_map_(case, expected, sort_results):
Expand Down

0 comments on commit 0358c81

Please sign in to comment.