11PEP: 769
2- Title: Add a 'default' keyword argument to 'attrgetter' and 'itemgetter '
2+ Title: Add a 'default' keyword argument to 'attrgetter', 'itemgetter' and 'getitem '
33Author: Facundo Batista <facundo@taniquetil.com.ar>
44Discussions-To: https://discuss.python.org/t/76419
55Status: Draft
@@ -12,8 +12,8 @@ Abstract
1212========
1313
1414This proposal aims to enhance the :mod: `operator ` module by adding a
15- ``default `` keyword argument to the ``attrgetter `` and ``itemgetter ``
16- functions. This addition would allow these functions to return a
15+ ``default `` keyword argument to the ``attrgetter ``, ``itemgetter `` and
16+ `` getitem `` functions. This addition would allow these functions to return a
1717specified default value when the targeted attribute or item is missing,
1818thereby preventing exceptions and simplifying code that handles optional
1919attributes or items.
@@ -31,6 +31,10 @@ Introducing a ``default`` parameter would streamline operations involving
3131optional attributes or items, reducing boilerplate code and enhancing
3232code clarity.
3333
34+ A similar situation occurs with ``getitem ``, with the added nuance that
35+ allowing the specification of a default value in this case would resolve
36+ a longstanding asymmetry with the :func: `getattr ` built-in function.
37+
3438
3539Rationale
3640=========
@@ -58,11 +62,16 @@ Proposed behaviors:
5862- **itemgetter **: ``f = itemgetter(2, default=XYZ) `` followed by
5963 ``f(obj) `` would return ``obj[2] `` if that is valid, else ``XYZ ``.
6064
61- This enhancement applies to single and multiple attribute/item
62- retrievals, with the default value returned for any missing attribute or
63- item.
65+ - **getitem **: ``getitem(obj, k, XYZ) `` or
66+ ``getitem(obj, k, default=XYZ) `` would return ``obj[k] `` if that is valid,
67+ else ``XYZ ``.
68+
69+ In the first two cases, the enhancement applies to single and multiple
70+ attribute/item retrievals, with the default value returned for any
71+ missing attribute or item.
6472
65- No functionality change is incorporated if ``default `` is not used.
73+ No functionality change is incorporated in any case if the extra
74+ default (keyword) argument is not used.
6675
6776
6877Examples for attrgetter
@@ -144,7 +153,32 @@ With this PEP, using the proposed ``default`` keyword::
144153 ('bar', 'XYZ')
145154
146155
147- .. _PEP 769 About Possible Implementations :
156+ Examples for getitem
157+ --------------------
158+
159+ The current behavior is unchanged::
160+
161+ >>> obj = ["foo", "bar", "baz"]
162+ >>> getitem(obj, 1)
163+ 'bar'
164+ >>> getitem(obj, 5)
165+ Traceback (most recent call last):
166+ File "<stdin>", line 1, in <module>
167+ IndexError: list index out of range
168+
169+
170+ With this PEP, using the proposed extra default, positionally or with
171+ a keyword::
172+
173+ >>> getitem(obj, 1, "XYZ")
174+ 'bar'
175+ >>> getitem(obj, 5, "XYZ")
176+ 'XYZ'
177+ >>> getitem(obj, 1, default="XYZ")
178+ 'bar'
179+ >>> getitem(obj, 5, default="XYZ")
180+ 'XYZ'
181+
148182
149183About Possible Implementations
150184------------------------------
@@ -163,37 +197,43 @@ be impossible to distinguish what it returned on each step when an
163197attribute chain is specified (e.g.
164198``attrgetter("foo.bar.baz", default=XYZ) ``).
165199
166- The implementation for ``itemgetter `` is not that easy. The more
167- straightforward way is also simple to define and
168- understand: attempting ``__getitem__ `` and catching a possible exception
169- (any of the three indicated in ``__getitem__ `` `reference `_). This way,
170- ``itemgetter(123, default=XYZ)(obj) `` would be equivalent to::
200+ The implementation for ``itemgetter `` and ``getitem `` is not that
201+ easy. The more straightforward way is also simple to define and
202+ understand: attempting ``__getitem__ `` and catching a possible
203+ exception (any of the three indicated in ``__getitem__ `` `reference `_).
204+ This way, ``itemgetter(123, default=XYZ)(obj) `` or
205+ ``getitem(obj, 123, default=XYZ) `` would be equivalent to::
171206
172207 try:
173208 value = obj[123]
174209 except (TypeError, IndexError, KeyError):
175210 value = XYZ
176211
177- However, this would be not as efficient as we'd want for certain cases,
178- e.g. using dictionaries where better performance is possible. A
179- more complex alternative would be::
212+ However, for performance reasons the implementation may look more
213+ like the following, which has the same exact behaviour::
180214
181- if isinstance (obj, dict) :
215+ if type (obj) == dict:
182216 value = obj.get(123, XYZ)
183217 else:
184218 try:
185219 value = obj[123]
186220 except (TypeError, IndexError, KeyError):
187221 value = XYZ
188222
189- While this provides better performance, it is more complicated to implement and explain. This is
190- the first case in the `Open Issues <PEP 769 Open Issues _>`__ section later.
223+ Note how the verification is about the exact type and not using
224+ ``isinstance ``; this is to ensure the exact behaviour, which would be
225+ impossible if the object is a user defined one that inherits ``dict ``
226+ but overwrites ``get `` (similar reason to not check if the object has
227+ a ``get `` method).
228+
229+ This way, performance is better but it's just an implementation detail,
230+ so we can keep the original explanation on how it behaves.
191231
192232
193233Corner Cases
194234------------
195235
196- Providing a ``default `` option would only work when accessing the
236+ Providing a ``default `` option would only work if accessing the
197237item/attribute would fail in the normal case. In other words, the
198238object accessed should not handle defaults itself.
199239
@@ -255,8 +295,8 @@ combinations of ``attrgetter`` and ``itemgetter``, e.g.::
255295 (1, 2)
256296
257297However, combining ``itemgetter `` or ``attrgetter `` is totally new
258- behavior and very complex to define. While not impossible, it is beyond the scope of
259- this PEP.
298+ behavior and very complex to define. While not impossible, it is beyond
299+ the scope of this PEP.
260300
261301In the end, having multiple default values was deemed overly complex and
262302potentially confusing, and a single ``default `` parameter was favored for
@@ -286,49 +326,7 @@ PEP.
286326Open Issues
287327===========
288328
289- Behavior Equivalence for ``itemgetter ``
290- ---------------------------------------
291-
292- For ``itemgetter ``, should it just attempt to
293- access the item and capture exceptions regardless of the object's API, or
294- should it first validate that the object provides a ``get `` method, and if so use it to
295- retrieve the item with a default? See examples in the `About Possible
296- Implementations <PEP 769 About Possible Implementations_> `__ subsection
297- above.
298-
299- This would help performance for the case of dictionaries, but would make
300- the ``default `` feature somewhat more difficult to explain, and a little
301- confusing if some object that is not a dictionary but still provides a ``get ``
302- method. Alternatively, we could call ``.get `` *only * if the
303- object is an instance of ``dict ``.
304-
305- In any case, it is desirable that we do *not * affect performance
306- at all if the ``default `` is not triggered. Checking for ``.get `` would
307- be faster for dicts, but implies doing a verification
308- in all cases. Using the try/except model would make it less efficient as possible
309- in the case of dictionaries, but only if the
310- default is not triggered.
311-
312-
313- Add a Default to ``getitem ``
314- ----------------------------
315-
316- It was proposed that we could also enhance ``getitem ``, as part of
317- this PEP, adding the ``default `` keyword to that function as well.
318-
319- This will not only improve ``getitem `` itself, but we would also gain
320- internal consistency in the ``operator `` module and in comparison with
321- the ``getattr `` builtin function, which also has a default.
322-
323- The definition could be as simple as the try/except proposed above, so
324- doing ``getitem(obj, name, default) `` would be equivalent to::
325-
326- try:
327- result = obj[name]
328- except (TypeError, IndexError, KeyError):
329- result = default
330-
331- (However see previous open issue about special case for dictionaries.)
329+ There are no open issues at this time.
332330
333331
334332How to Teach This
0 commit comments