diff --git a/peps/pep-0769.rst b/peps/pep-0769.rst index ae9568a2ed8..38b8f82ed83 100644 --- a/peps/pep-0769.rst +++ b/peps/pep-0769.rst @@ -1,5 +1,5 @@ PEP: 769 -Title: Add a 'default' keyword argument to 'attrgetter' and 'itemgetter' +Title: Add a 'default' keyword argument to 'attrgetter', 'itemgetter' and 'getitem' Author: Facundo Batista Discussions-To: https://discuss.python.org/t/76419 Status: Draft @@ -12,8 +12,8 @@ Abstract ======== This proposal aims to enhance the :mod:`operator` module by adding a -``default`` keyword argument to the ``attrgetter`` and ``itemgetter`` -functions. This addition would allow these functions to return a +``default`` keyword argument to the ``attrgetter``, ``itemgetter`` and +``getitem`` functions. This addition would allow these functions to return a specified default value when the targeted attribute or item is missing, thereby preventing exceptions and simplifying code that handles optional attributes or items. @@ -31,6 +31,10 @@ Introducing a ``default`` parameter would streamline operations involving optional attributes or items, reducing boilerplate code and enhancing code clarity. +A similar situation occurs with ``getitem``, with the added nuance that +allowing the specification of a default value in this case would resolve +a longstanding asymmetry with the :func:`getattr` built-in function. + Rationale ========= @@ -58,11 +62,16 @@ Proposed behaviors: - **itemgetter**: ``f = itemgetter(2, default=XYZ)`` followed by ``f(obj)`` would return ``obj[2]`` if that is valid, else ``XYZ``. -This enhancement applies to single and multiple attribute/item -retrievals, with the default value returned for any missing attribute or -item. +- **getitem**: ``getitem(obj, k, XYZ)`` or + ``getitem(obj, k, default=XYZ)`` would return ``obj[k]`` if that is valid, + else ``XYZ``. + +In the first two cases, the enhancement applies to single and multiple +attribute/item retrievals, with the default value returned for any +missing attribute or item. -No functionality change is incorporated if ``default`` is not used. +No functionality change is incorporated in any case if the extra +default (keyword) argument is not used. Examples for attrgetter @@ -144,7 +153,32 @@ With this PEP, using the proposed ``default`` keyword:: ('bar', 'XYZ') -.. _PEP 769 About Possible Implementations: +Examples for getitem +-------------------- + +The current behavior is unchanged:: + + >>> obj = ["foo", "bar", "baz"] + >>> getitem(obj, 1) + 'bar' + >>> getitem(obj, 5) + Traceback (most recent call last): + File "", line 1, in + IndexError: list index out of range + + +With this PEP, using the proposed extra default, positionally or with +a keyword:: + + >>> getitem(obj, 1, "XYZ") + 'bar' + >>> getitem(obj, 5, "XYZ") + 'XYZ' + >>> getitem(obj, 1, default="XYZ") + 'bar' + >>> getitem(obj, 5, default="XYZ") + 'XYZ' + About Possible Implementations ------------------------------ @@ -163,22 +197,22 @@ be impossible to distinguish what it returned on each step when an attribute chain is specified (e.g. ``attrgetter("foo.bar.baz", default=XYZ)``). -The implementation for ``itemgetter`` is not that easy. The more -straightforward way is also simple to define and -understand: attempting ``__getitem__`` and catching a possible exception -(any of the three indicated in ``__getitem__`` `reference`_). This way, -``itemgetter(123, default=XYZ)(obj)`` would be equivalent to:: +The implementation for ``itemgetter`` and ``getitem`` is not that +easy. The more straightforward way is also simple to define and +understand: attempting ``__getitem__`` and catching a possible +exception (any of the three indicated in ``__getitem__`` `reference`_). +This way, ``itemgetter(123, default=XYZ)(obj)`` or +``getitem(obj, 123, default=XYZ)`` would be equivalent to:: try: value = obj[123] except (TypeError, IndexError, KeyError): value = XYZ -However, this would be not as efficient as we'd want for certain cases, -e.g. using dictionaries where better performance is possible. A -more complex alternative would be:: +However, for performance reasons the implementation may look more +like the following, which has the same exact behaviour:: - if isinstance(obj, dict): + if type(obj) == dict: value = obj.get(123, XYZ) else: try: @@ -186,14 +220,20 @@ more complex alternative would be:: except (TypeError, IndexError, KeyError): value = XYZ -While this provides better performance, it is more complicated to implement and explain. This is -the first case in the `Open Issues `__ section later. +Note how the verification is about the exact type and not using +``isinstance``; this is to ensure the exact behaviour, which would be +impossible if the object is a user defined one that inherits ``dict`` +but overwrites ``get`` (similar reason to not check if the object has +a ``get`` method). + +This way, performance is better but it's just an implementation detail, +so we can keep the original explanation on how it behaves. Corner Cases ------------ -Providing a ``default`` option would only work when accessing the +Providing a ``default`` option would only work if accessing the item/attribute would fail in the normal case. In other words, the object accessed should not handle defaults itself. @@ -255,8 +295,8 @@ combinations of ``attrgetter`` and ``itemgetter``, e.g.:: (1, 2) However, combining ``itemgetter`` or ``attrgetter`` is totally new -behavior and very complex to define. While not impossible, it is beyond the scope of -this PEP. +behavior and very complex to define. While not impossible, it is beyond +the scope of this PEP. In the end, having multiple default values was deemed overly complex and potentially confusing, and a single ``default`` parameter was favored for @@ -286,49 +326,7 @@ PEP. Open Issues =========== -Behavior Equivalence for ``itemgetter`` ---------------------------------------- - -For ``itemgetter``, should it just attempt to -access the item and capture exceptions regardless of the object's API, or -should it first validate that the object provides a ``get`` method, and if so use it to -retrieve the item with a default? See examples in the `About Possible -Implementations `__ subsection -above. - -This would help performance for the case of dictionaries, but would make -the ``default`` feature somewhat more difficult to explain, and a little -confusing if some object that is not a dictionary but still provides a ``get`` -method. Alternatively, we could call ``.get`` *only* if the -object is an instance of ``dict``. - -In any case, it is desirable that we do *not* affect performance -at all if the ``default`` is not triggered. Checking for ``.get`` would -be faster for dicts, but implies doing a verification -in all cases. Using the try/except model would make it less efficient as possible -in the case of dictionaries, but only if the -default is not triggered. - - -Add a Default to ``getitem`` ----------------------------- - -It was proposed that we could also enhance ``getitem``, as part of -this PEP, adding the ``default`` keyword to that function as well. - -This will not only improve ``getitem`` itself, but we would also gain -internal consistency in the ``operator`` module and in comparison with -the ``getattr`` builtin function, which also has a default. - -The definition could be as simple as the try/except proposed above, so -doing ``getitem(obj, name, default)`` would be equivalent to:: - - try: - result = obj[name] - except (TypeError, IndexError, KeyError): - result = default - -(However see previous open issue about special case for dictionaries.) +There are no open issues at this time. How to Teach This