
Pythons: What are your thoughts on the concept of a `defaultattrgetter`? It would be to operator.attrgetter what getattr(foo, x, default) is to getattr(foo, x). I dont like that attrgetter requires the attribute to exist or else the getter be wrapped in a try catch with respect to how defaultdict solves roughly the same problem for dictionary keys. The semantics could be something such as: from operator import defaultattrgetter _x = defaultattrgetter({'x': 0}) _y = defaultattrgetter({'y': 1}) - or - _xy = defaultattrgetter({'x': 0, 'y': 1}) One use case I am thinking of is functions that may be decorated with attributes x and/or y. Obviously a python implementation of defaultattrgetter would be trivial to implement but one of the benefits of these functions is the speed. It also seems like it would fit in well with the rest of the operator module. Generally speaking, would anyone else have a use for this? - John

On Wed, Dec 21, 2011 at 6:33 PM, Steven D'Aprano <steve@pearwood.info> wrote:
Why create a new function for it? Why not just give attrgetter a keyword only argument default?
I need to revise my example. It should be: _x = defaultattrgetter(('x', 0)) _xy = defaultattrgetter(('x', 0), ('y', 1)) Which is what I had originally but I was too quick to change it in the course of writing thinking the dict notation looked cleaner. But, since the argument order matters a dict wont work. The same applies to using keyword arguments. I'm not sure if there is a clean way to add this type of functionally to attrgetter without strings being a special case.

John O'Connor, 22.12.2011 08:05:
On Wed, Dec 21, 2011 at 6:33 PM, Steven D'Aprano wrote:
Why create a new function for it? Why not just give attrgetter a keyword only argument default?
I need to revise my example. It should be: _x = defaultattrgetter(('x', 0)) _xy = defaultattrgetter(('x', 0), ('y', 1))
Which is what I had originally but I was too quick to change it in the course of writing thinking the dict notation looked cleaner. But, since the argument order matters a dict wont work. The same applies to using keyword arguments. I'm not sure if there is a clean way to add this type of functionally to attrgetter without strings being a special case.
I don't consider it a major use case to be able to use different default return values for different steps in the lookup process. If you want that, write your own lookup function, that's trivial enough. If such a feature gets added (which would be for Python 3.3 or later), I second Steven's proposal of making it a keyword argument, i.e. lookup_a_b_c = operator.attrgetter('a', 'b', 'c', default=123) Stefan

On 2011-12-22, at 08:23 , Stefan Behnel wrote:
John O'Connor, 22.12.2011 08:05:
On Wed, Dec 21, 2011 at 6:33 PM, Steven D'Aprano wrote:
Why create a new function for it? Why not just give attrgetter a keyword only argument default?
I need to revise my example. It should be: _x = defaultattrgetter(('x', 0)) _xy = defaultattrgetter(('x', 0), ('y', 1))
Which is what I had originally but I was too quick to change it in the course of writing thinking the dict notation looked cleaner. But, since the argument order matters a dict wont work. The same applies to using keyword arguments. I'm not sure if there is a clean way to add this type of functionally to attrgetter without strings being a special case.
I don't consider it a major use case to be able to use different default return values for different steps in the lookup process. If you want that, write your own lookup function, that's trivial enough.
If such a feature gets added (which would be for Python 3.3 or later), I second Steven's proposal of making it a keyword argument, i.e.
lookup_a_b_c = operator.attrgetter('a', 'b', 'c', default=123) The problem with that is … does the default value apply to all three attributes? What if you need a default value for one but not the others, or different values for all three? Does this behavior really make sense?
Maybe a dict of default values, at least when extracting more than one attribute?

Masklinn, 22.12.2011 08:57:
On 2011-12-22, at 08:23 , Stefan Behnel wrote:
John O'Connor, 22.12.2011 08:05:
On Wed, Dec 21, 2011 at 6:33 PM, Steven D'Aprano wrote:
Why create a new function for it? Why not just give attrgetter a keyword only argument default?
I need to revise my example. It should be: _x = defaultattrgetter(('x', 0)) _xy = defaultattrgetter(('x', 0), ('y', 1))
Which is what I had originally but I was too quick to change it in the course of writing thinking the dict notation looked cleaner. But, since the argument order matters a dict wont work. The same applies to using keyword arguments. I'm not sure if there is a clean way to add this type of functionally to attrgetter without strings being a special case.
I don't consider it a major use case to be able to use different default return values for different steps in the lookup process. If you want that, write your own lookup function, that's trivial enough.
If such a feature gets added (which would be for Python 3.3 or later), I second Steven's proposal of making it a keyword argument, i.e.
lookup_a_b_c = operator.attrgetter('a', 'b', 'c', default=123) The problem with that is … does the default value apply to all three attributes?
That's what I meant: it should hit the most common case and leave the rest to the user to handle.
What if you need a default value for one but not the others, or different values for all three? Does this behavior really make sense?
What would be the use case? Would you want the lookup process to continue on the default argument if an intermediate attribute is not found? And if a subsequent lookup fails? Take the corresponding default value again and keep looking up on it? That feels rather unpredictable from a users' point of view. I think the idea is that you ask for an attribute, which (in the less common cases) happens to be a multi-step lookup, and if the attribute is not found, you want it to return a default value *for the attribute you requested*, i.e. not a different value for any of the intermediate attributes, only a specific value that corresponds to the last attribute in the lookup chain. In the 99.9% case, that will be something like None or a "keep going, I don't care" value, not something that depends on the lookup path in any way. I don't think the 0.1% case where you want more than that is worth a substantially more complicated API. Stefan

On 2011-12-22, at 09:09 , Stefan Behnel wrote:
What if you need a default value for one but not the others, or different values for all three? Does this behavior really make sense?
What would be the use case? Would you want the lookup process to continue on the default argument if an intermediate attribute is not found? And if a subsequent lookup fails? Take the corresponding default value again and keep looking up on it? That feels rather unpredictable from a users' point of view.
A multiple-*args attrgetter fetches different arguments, for a "lookup process" you need a dotted path (attrgetter('foo.bar.baz')). For that one a default value returned after any lookup failure makes sense, but it's not what attrgetter('foo', 'bar', 'baz') does:
from collections import namedtuple from operator import attrgetter r = namedtuple('T', 'a b c d e')(1, 2, 3, 4, 5) attrgetter('a.__index__')(r) <method-wrapper '__index__' of int object at 0x10020b1e8> attrgetter('a', 'b', 'e')(r) (1, 2, 5)
And for the second case, you'd need to be able to specify a default value for each of a, b and e.
I think the idea is that you ask for an attribute, which (in the less common cases) happens to be a multi-step lookup, and if the attribute is not found, you want it to return a default value *for the attribute you requested*, i.e. not a different value for any of the intermediate attributes Right, we're talking about different things and as noted above I think you're figuring the behavior of attrgetter(arg0, arg1, …) wrong.
I don't think the 0.1% case where you want more than that is worth a substantially more complicated API. Thing is, I don't know about others but my main use of attrgetter *by far* is to fetch multiple attributes simultanously. When fetching a single attribute (even with multiple steps), it's often (if not always) shorter to use a lambda with an attribute lookup.

Masklinn, 22.12.2011 09:25:
On 2011-12-22, at 09:09 , Stefan Behnel wrote:
What if you need a default value for one but not the others, or different values for all three? Does this behavior really make sense?
What would be the use case? Would you want the lookup process to continue on the default argument if an intermediate attribute is not found? And if a subsequent lookup fails? Take the corresponding default value again and keep looking up on it? That feels rather unpredictable from a users' point of view.
A multiple-*args attrgetter fetches different arguments, for a "lookup process" you need a dotted path (attrgetter('foo.bar.baz')).
Ah, thanks, I remembered that incorrectly then. In that case, I vote -1 for adding the feature in the first place, because it would complicate an otherwise simple API too much and the current syntax proposals are too verbose and too hard to read. Stefan

Stefan Behnel wrote:
Masklinn, 22.12.2011 09:25:
On 2011-12-22, at 09:09 , Stefan Behnel wrote:
What if you need a default value for one but not the others, or different values for all three? Does this behavior really make sense?
What would be the use case? Would you want the lookup process to continue on the default argument if an intermediate attribute is not found? And if a subsequent lookup fails? Take the corresponding default value again and keep looking up on it? That feels rather unpredictable from a users' point of view.
A multiple-*args attrgetter fetches different arguments, for a "lookup process" you need a dotted path (attrgetter('foo.bar.baz')).
Ah, thanks, I remembered that incorrectly then.
In that case, I vote -1 for adding the feature in the first place, because it would complicate an otherwise simple API too much and the current syntax proposals are too verbose and too hard to read.
I still like the addition, I think
attrgetter("last_name", default="unknown")(None) 'unknown'
would be the most common use case by far and
location = None attrgetter("x", "y", "z", default=0)(location) (0, 0, 0) attrgetter("x.unit", default=None)(location) None # don't know if there's no location.x or no location.x.unit
are useful and easy enough to grasp.

I think the idea is that you ask for an attribute, which (in the less common cases) happens to be a multi-step lookup, and if the attribute is not found, you want it to return a default value *for the attribute you requested*, i.e. not a different value for any of the intermediate attributes, only a specific value that corresponds to the last attribute in the lookup chain. In the 99.9% case, that will be something like None or a "keep going, I don't care" value, not something that depends on the lookup path in any way.
I don't think the 0.1% case where you want more than that is worth a substantially more complicated API.
I could settle for the default= but I think it is too simple and thus incomplete. I think it is extreme to say that one default argument covers 99.9% use case. I do think you are right about the ignored path but that path may use more than one condition. _xy = defaultattrgetter(('x', True), ('y', False)) x, y = _xy(foo) # usually do x unless told otherwise if x: ... # usually dont do y if y: ... I guess one alternative could be: _xy = attrgetter('x', 'y', defaults={'x': True, 'y': False}) but that just looks like DRY without the D.

On Dec 22, 2011 9:56 AM, "John O'Connor" <jxo6948@rit.edu> wrote:
I think the idea is that you ask for an attribute, which (in the less
common
cases) happens to be a multi-step lookup, and if the attribute is not found, you want it to return a default value *for the attribute you requested*, i.e. not a different value for any of the intermediate attributes, only a specific value that corresponds to the last attribute in the lookup chain. In the 99.9% case, that will be something like None or a "keep going, I don't care" value, not something that depends on the lookup path in any way.
I don't think the 0.1% case where you want more than that is worth a substantially more complicated API.
I could settle for the default= but I think it is too simple and thus incomplete. I think it is extreme to say that one default argument covers 99.9% use case. I do think you are right about the ignored path but that path may use more than one condition.
_xy = defaultattrgetter(('x', True), ('y', False)) x, y = _xy(foo) # usually do x unless told otherwise if x: ... # usually dont do y if y: ...
I guess one alternative could be: _xy = attrgetter('x', 'y', defaults={'x': True, 'y': False})
but that just looks like DRY without the D.
You could have attrgetter('x', 'y', defaults=(True, False)) to put the D back in. Arnaud

22.12.11 11:55, John O'Connor написав(ла):
I do think you are right about the ignored path but that path may use more than one condition.
_xy = defaultattrgetter(('x', True), ('y', False)) x, y = _xy(foo) # usually do x unless told otherwise if x: ... # usually dont do y if y: ...
I guess one alternative could be: _xy = attrgetter('x', 'y', defaults={'x': True, 'y': False})
Or _xy = attrgetter('x', 'y', defaults=(True, False)) Sometimes, however, will be better to specify not value, but factory (when value is list, dictionary, or complex object), as for defauldict. _xyz = defaultattrgetter('x', ('y', int, 123), ('z', list)) x has not default value, y has default value int(123), z has default value list().

Once again, please don't get so enamoured of higher order functions that you miss the obvious solution: *just write a new function that does exactly what you want*. "Swiss Army APIs" are not a good thing. Sometimes they're fairly unavoidable because they're exposing a complex underlying operation with a lot of moving parts (e.g. subprocess.Popen), but other times they're useless cruft that is so hard to remember that most people never bother with them, as just writing the custom function is significantly easier. So, for the 3 examples given: def _x(arg): return getattr(arg, 'x', 0) def _y(arg): return getattr(arg, 'y', 1) def _xy(arg): return _x(arg), _y(arg) Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
participants (8)
-
Arnaud Delobelle
-
John O'Connor
-
Masklinn
-
Nick Coghlan
-
Peter Otten
-
Serhiy Storchaka
-
Stefan Behnel
-
Steven D'Aprano