1
1
PEP: 769
2
- Title: Add a 'default' keyword argument to 'attrgetter' and 'itemgetter '
2
+ Title: Add a 'default' keyword argument to 'attrgetter', 'itemgetter' and 'getitem '
3
3
Author: Facundo Batista <
[email protected] >
4
4
Discussions-To: https://discuss.python.org/t/76419
5
5
Status: Draft
@@ -12,8 +12,8 @@ Abstract
12
12
========
13
13
14
14
This 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
17
17
specified default value when the targeted attribute or item is missing,
18
18
thereby preventing exceptions and simplifying code that handles optional
19
19
attributes or items.
@@ -31,6 +31,10 @@ Introducing a ``default`` parameter would streamline operations involving
31
31
optional attributes or items, reducing boilerplate code and enhancing
32
32
code clarity.
33
33
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
+
34
38
35
39
Rationale
36
40
=========
@@ -58,11 +62,16 @@ Proposed behaviors:
58
62
- **itemgetter **: ``f = itemgetter(2, default=XYZ) `` followed by
59
63
``f(obj) `` would return ``obj[2] `` if that is valid, else ``XYZ ``.
60
64
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.
64
72
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.
66
75
67
76
68
77
Examples for attrgetter
@@ -144,7 +153,32 @@ With this PEP, using the proposed ``default`` keyword::
144
153
('bar', 'XYZ')
145
154
146
155
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
+
148
182
149
183
About Possible Implementations
150
184
------------------------------
@@ -163,37 +197,43 @@ be impossible to distinguish what it returned on each step when an
163
197
attribute chain is specified (e.g.
164
198
``attrgetter("foo.bar.baz", default=XYZ) ``).
165
199
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::
171
206
172
207
try:
173
208
value = obj[123]
174
209
except (TypeError, IndexError, KeyError):
175
210
value = XYZ
176
211
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::
180
214
181
- if isinstance (obj, dict) :
215
+ if type (obj) == dict:
182
216
value = obj.get(123, XYZ)
183
217
else:
184
218
try:
185
219
value = obj[123]
186
220
except (TypeError, IndexError, KeyError):
187
221
value = XYZ
188
222
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.
191
231
192
232
193
233
Corner Cases
194
234
------------
195
235
196
- Providing a ``default `` option would only work when accessing the
236
+ Providing a ``default `` option would only work if accessing the
197
237
item/attribute would fail in the normal case. In other words, the
198
238
object accessed should not handle defaults itself.
199
239
@@ -255,8 +295,8 @@ combinations of ``attrgetter`` and ``itemgetter``, e.g.::
255
295
(1, 2)
256
296
257
297
However, 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.
260
300
261
301
In the end, having multiple default values was deemed overly complex and
262
302
potentially confusing, and a single ``default `` parameter was favored for
@@ -286,49 +326,7 @@ PEP.
286
326
Open Issues
287
327
===========
288
328
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.
332
330
333
331
334
332
How to Teach This
0 commit comments