diff --git a/peps/pep-0769.rst b/peps/pep-0769.rst index 345e3b83e9b..7165caf592e 100644 --- a/peps/pep-0769.rst +++ b/peps/pep-0769.rst @@ -10,7 +10,7 @@ Python-Version: 3.14 Abstract ======== -This proposal aims to enhance the ``operator`` module by adding a +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 specified default value when the targeted attribute or item is missing, @@ -48,7 +48,7 @@ value for all cases (more about this below in `Rejected Ideas Specification ============= -Proposed behaviours: +Proposed behaviors: - **attrgetter**: ``f = attrgetter("name", default=XYZ)`` followed by ``f(obj)`` would return ``obj.name`` if the attribute exists, else @@ -67,7 +67,7 @@ No functionality change is incorporated if ``default`` is not used. Examples for attrgetter ----------------------- -Current behaviour, no changes introduced:: +The current behavior is unchanged:: >>> class C: ... class D: @@ -95,7 +95,7 @@ Current behaviour, no changes introduced:: File "", line 1, in AttributeError: type object 'D' has no attribute 'badname' -Using ``default``:: +With this PEP, using the proposed ``default`` keyword:: >>> attrgetter("D", default="noclass")(C) @@ -114,7 +114,7 @@ Using ``default``:: Examples for itemgetter ----------------------- -Current behaviour, no changes introduced:: +The current behavior is unchanged:: >>> obj = ["foo", "bar", "baz"] >>> itemgetter(1)(obj) @@ -131,7 +131,7 @@ Current behaviour, no changes introduced:: IndexError: list index out of range -Using ``default``:: +With this PEP, using the proposed ``default`` keyword:: >>> itemgetter(1, default="XYZ")(obj) 'bar' @@ -148,8 +148,8 @@ Using ``default``:: About Possible Implementations ------------------------------ -For the case of ``attrgetter`` is quite direct: it implies using -``getattr`` catching a possible ``AttributeError``. So +The implementation of ``attrgetter`` is quite direct: it implies using +``getattr`` and catching a possible ``AttributeError``. So ``attrgetter("name", default=XYZ)(obj)`` would be like:: try: @@ -157,15 +157,15 @@ For the case of ``attrgetter`` is quite direct: it implies using except (TypeError, IndexError, KeyError): value = XYZ -Note we cannot rely on using ``gettattr`` with a default value, as would +Note we cannot rely on using ``getattr`` with a default value, as it would be impossible to distinguish what it returned on each step when an attribute chain is specified (e.g. ``attrgetter("foo.bar.baz", default=XYZ)``). -For the case of ``itemgetter`` it's not that easy. The more -straightforward way is similar to above, also simple to define and +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, +(any of the three indicated in ``__getitem__`` `reference`_). This way, ``itemgetter(123, default=XYZ)(obj)`` would be equivalent to:: try: @@ -173,8 +173,8 @@ understand: attempting ``__getitem__`` and catching a possible exception except (TypeError, IndexError, KeyError): value = XYZ -However, this would be not as efficient as we'd want for particular cases, -e.g. using dictionaries where particularly good performance is desired. A +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:: if isinstance(obj, dict): @@ -185,16 +185,16 @@ more complex alternative would be:: except (TypeError, IndexError, KeyError): value = XYZ -Better performance, more complicated to implement and explain. This is +While this provides better performance, it is more complicated to implement and explain. This is the first case in the `Open Issues `__ section later. Corner Cases ------------ -Providing a ``default`` option would only work when accessing to the -item/attribute would fail in a regular situation. In other words, the -object accessed should not handle defaults theirselves. +Providing a ``default`` option would only work when accessing the +item/attribute would fail in the normal case. In other words, the +object accessed should not handle defaults itself. For example, the following would be redundant/confusing because ``defaultdict`` will never error out when accessing the item:: @@ -205,8 +205,8 @@ For example, the following would be redundant/confusing because >>> itemgetter("foo", default=-1)(dd) 0 -The same applies to any user built object that overloads ``__getitem__`` -or ``__getattr__`` implementing fallbacks. +The same applies to any user defined object that overloads ``__getitem__`` +or ``__getattr__`` implementing its own fallbacks. .. _PEP 769 Rejected Ideas: @@ -221,30 +221,29 @@ The idea of allowing multiple default values for multiple attributes or items was considered. Two alternatives were discussed, using an iterable that must have the -same quantity of items than parameters given to +same quantity of items as parameters given to ``attrgetter``/``itemgetter``, or using a dictionary with keys matching those names passed to ``attrgetter``/``itemgetter``. -The really complex thing to solve in these casse, that would make the -feature hard to explain and with confusing corners, is what would happen -if an iterable or dictionary is the *unique* default desired for all +The really complex thing to solve here (making the +feature hard to explain and with confusing corner cases), is what would happen +if an iterable or dictionary is the *actual* default desired for all items. For example:: - >>> itemgetter("a", default=(1, 2)({}) + >>> itemgetter("a", default=(1, 2))({}) (1, 2) >>> itemgetter("a", "b", default=(1, 2))({}) ((1, 2), (1, 2)) If we allow "multiple default values" using ``default``, the first case -in the example above would raise an exception because more items in the -default than names, and the second case would return ``(1, 2))``. This is -why emerged the possibility of using a different name for multiple -defaults (``defaults``, which is expressive but maybe error prone because -too similar to ``default``). +in the example above would raise an exception because there are more items +than names in the default, and the second case would return ``(1, 2))``. This is +why we considered the possibility of using a different name for multiple +defaults (e.g. ``defaults``, which is expressive but maybe error prone because +it is too similar to ``default``). -As part of this conversation there was another proposal that would enable -multiple defaults, which is allowing combinations of ``attrgetter`` and -``itemgetter``, e.g.:: +Another proposal that would enable multiple defaults, is allowing +combinations of ``attrgetter`` and ``itemgetter``, e.g.:: >>> ig_a = itemgetter("a", default=1) >>> ig_b = itemgetter("b", default=2) @@ -254,11 +253,11 @@ multiple defaults, which is allowing combinations of ``attrgetter`` and >>> ig_combined({}) (1, 2) -However, combining ``itemgetter`` or ``attrgetter`` is a totally new -behaviour very complex to define, not impossible, but beyond the scope of +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. -At the end having multiple default values was deemed overly complex and +In the end, having multiple default values was deemed overly complex and potentially confusing, and a single ``default`` parameter was favored for simplicity and predictability. @@ -266,8 +265,8 @@ simplicity and predictability. Tuple Return Consistency ------------------------ -Another rejected proposal was adding a flag to always return tuple -regardless of how many keys/names/indices were sourced to arguments. +Another rejected proposal was adding a flag to always return a tuple +regardless of how many keys/names/indices were given. E.g.:: >>> letters = ["a", "b", "c"] @@ -276,8 +275,8 @@ E.g.:: >>> itemgetter(1, 2, return_tuple=True)(letters) ('b', 'c') -This would be of a little help for multiple default values consistency, -but requires further discussion and for sure is out of the scope of this +This would be of little help for multiple default values consistency, +requiring further discussion, and is out of the scope of this PEP. @@ -286,39 +285,39 @@ PEP. Open Issues =========== -Behaviour Equivalence for ``itemgetter`` ----------------------------------------- +Behavior Equivalence for ``itemgetter`` +--------------------------------------- -We need to define how ``itemgetter`` would behave, if just attempt to -access the item and capture exceptions no matter which the object, or -validate first if the object provides a ``get`` method and use it to -retrieve the item with a default. See examples in the `About Possible +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 provides a ``get`` -method is used. Alternatively, we could call ``.get`` *only* if the +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, a desirable situation is that we do *not* affect performance +In any case, it is desirable that we do *not* affect performance at all if the ``default`` is not triggered. Checking for ``.get`` would -get the default faster in case of dicts, but implies doing a verification -in all cases. Using the try/except model would make it not as fast as it -could in the case of dictionaries, but would not introduce delays if the +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 the of -this PEP, adding ``default`` also to it. +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 that also has a default. +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:: @@ -328,15 +327,15 @@ doing ``getitem(obj, name, default)`` would be equivalent to:: except (TypeError, IndexError, KeyError): result = default -(However see previous open issue about special case for dictionaries) +(However see previous open issue about special case for dictionaries.) How to Teach This ================= -As the basic behaviour is not modified, this new ``default`` can be +As the basic behavior is not modified, this new ``default`` can be avoided when teaching ``attrgetter`` and ``itemgetter`` for the first -time, and can be introduced only when the functionality need arises. +time. It can be introduced only when the functionality is needed. Backwards Compatibility @@ -355,6 +354,8 @@ Security Implications Introducing a ``default`` parameter does not inherently introduce security vulnerabilities. +.. _reference: https://docs.python.org/3/reference/datamodel.html#object.__getitem__ +.. _module: https://docs.python.org/3/library/operator.html Copyright =========