Allow separators in `attrgetter`

Enhancement

I propose an enhancement based on this issue #46151.

Currently attrgetter accepts dotted path while getting attributes from an object. I propose it should not only accept dotted paths, but other separators as well. Like this:

operator.attrgetter(obj, "a.b")  # default behavior
operator.attrgetter(obj, "a__b", separator='__')  # enhanced behavior

Pitch

Allowing different types of separators in attrgetter will help users by a nested attribute path which is not dotted (could be something else such as underscore, or slash).

For example, in Django, they use __ as nested relation in ORM. Hence if we define the following code a nested relation (simplified example):

class B:
     name = 'A'

class A:
  b = B()

Then in Django, you should be able to get data for B from A (only in ORM, not in actual objects) then you can use the following code.

A.objects.values('b__name')

If we allow separators in attrgetter, then we can resonate this behavior at object level.

>>a = A.objects.create(**kwargs)  # its an example relevant to Django
>>print(attrgetter(a, 'b__name'))
A

If we allow having separator, it will be bit more consistent for users of attrgetter and it should be a simple enough change.

I am dubious about this.

As noted in issue #46151, the operator module was intended to and generally does expose internal functions, as they are coded in C in CPython, to Python itself. That issue created a small special-case exception to facilitate the use of attrgetter as a key function in .sort and other methods and functions. The previous option was to instead write a lambda function. I do not believe that #46151 intended to generally open operator and attrgetter to additions beyond the functions exposed.

attrgetter(string) returns a function meant to be called with an object that has an attribute named by the string. In the example above, attrgetter(a, 'b__name') must instead be attrgetter('b__name')(a). If one is passing a string linteral, as is the usual case I think, one can just as well, and even easier, pass the normal, legal python syntax. attrgetter('b.name')(a) works now, as I just verified.

If one wants to pass python object attribute names in string variables with app-specific non-pyobject syntax, such as s='b__name', it is not unreasonable to have the app or app user provide a conversion function. In this case,

def at(s): return s.replace('__', '.')  # Convert Django ORM attr name to python object dotted name.
j = 'b__name'
attrgetter(at(j))(a)
# prints 'A' (verified)

(The use of .replace was suggested by sobolevn in a comment.

1 Like