Operator attrgetter/itemgetter for setting

When operator module was implemented, specifically attrgetter and itemgetter was there a specific reason for not the equivalent attrsetter and itemsetter?

Proposed attrsetter syntax

Instantiation f = attrsetter('name', 'date') then calling f(b, "John", "30-Oct-2022") would return None and set b.name to "John" and b.date to "30-Oct-2022".

Similar syntax for itemsetter

Instantiation f = itemetter(0, 1) then calling f(b, "John", "30-Oct-2022") would return None and set b[0] to "John" and b[1] to "30-Oct-2022".

attrgetter and itemgetter were added because they are useful in many common cases: in map(), filter(), list.sort(), some itertools functions (and later in min(), max(), the heapq module). You can use a lambda instead, but these helpers were presumably faster and, for some people, more clear.

What are the use cases for your functions and how common are they?

1 Like

My original use case was overwriting fields from one dataclass to another. In other words, I have 2 dataclasses who fields intersect but did not overlap completely. This involved me expecting to find an attrsetter in operator module but not.

Dumbed down example:

Two dataclasses that share common fields. I want to transfer values from one to another. Presume we have attrsetter and it functions how I defined it in OP.

 from operator import attrgetter, attrsetter

import dataclasses as dc

@dc.dataclass
class Foo:
    a: int
    b: int
    c: float

@dc.dataclass
class Bar:
     a: int
     b: int
     d: str


foo = Foo(1, 2, 3.14)
bar = Bar(4, 5, "John")

get_ab = attrgetter("a", "b")
set_ab = attrsetter("a", "b")

set_ab(foo, *get_ab(bar))

Certainly doable with a function and easily extendable to collections of these dataclasses but that was the original use case.

Other times I’ve wanted for something like attrsetter was with nested pydantic models. I would expect attrsetter to behave similarly to attrgetter where it can resolve nested attribute access, setting the value on the final object.

While I never needed an itemsetter, my thought of it being added was out of consistency. I could see a similar case being had. Two containers with the need to update specific positions from one to another.

You can just write

foo.a = bar.a
foo.b = bar.b

Or if you have a lot of attributes needed:

for name in ['a', 'b', 'c']:
    setattr(foo, name, getattr(bar, name))

Personally, I have not found attrgetter very useful, and have never missed attrsetter.

I think if there is a good use-case for attrsetter, it would have to be some sort of functional programming idiom; otherwise for procedural-style code, it is just as easy to use setattr. The trouble is, functional idioms rarely operate by side-effect.

2 Likes

I was expecting the “there’s not enough justification to justify the maintenance burden” response.

To the functional programming aspect, I myself don’t even attempt functional style programming in python all that much, as such most of operator module is of very little use to me. I have however found a lot of use in attrgetter specifically for nested dataclasses or recursive pydantic models.

From pydantic’s docs

from typing import List, Optional
from pydantic import BaseModel
from operator import attrgetter


class Foo(BaseModel):
    count: int
    size: Optional[float] = None


class Bar(BaseModel):
    apple = 'x'
    banana = 'y'


class Spam(BaseModel):
    foo: Foo
    bars: List[Bar]


m = Spam(foo={'count': 4}, bars=[{'apple': 'x1'}, {'apple': 'x2'}])
print(attrgetter("foo.count", "bars.apple")(m))

Be able to do the reverse, to set would be incredible useful in my opinion. Yes, I could in fact write a function for it or just directly access as Serhiy suggests but at what level of nesting does it become more appropriate to write a throw away function.