operator.attrgetter: Sort Objects by Attributes

Published on: September 2, 2026
Reading time: 4 minutes
Software developer organizing object data with Python operator.attrgetter

When Python code works with lists of objects, a frequent task is sorting, grouping, selecting, or transforming records according to an attribute. The usual solution is a lambda such as lambda item: item.name. It is valid and familiar, but the standard library offers a more declarative alternative: operator.attrgetter. It creates a callable dedicated to retrieving attributes, including nested paths and multiple fields.

This guide explains how Python operator.attrgetter works, how to use it with sorted, min, max, and itertools.groupby, how it differs from itemgetter, and when a lambda or named function remains a better choice.

What operator.attrgetter does

attrgetter lives in the operator module. You pass one or more attribute names and receive a callable.

from operator import attrgetter
get_name = attrgetter("name")
print(get_name(user))

The returned object can be passed directly as a key function. This makes the intention explicit: the key is an attribute lookup rather than an arbitrary computation.

Sorting objects by one attribute

from dataclasses import dataclass
from operator import attrgetter

@dataclass
class Product:
    name: str
    price: float
    stock: int

products = [
    Product("Keyboard", 180.0, 8),
    Product("Mouse", 90.0, 15),
    Product("Monitor", 1200.0, 4),
]

by_price = sorted(products, key=attrgetter("price"))

This is equivalent to sorted(products, key=lambda p: p.price). For a broader explanation of keys, stability, and reverse order, read the Python sort versus sorted guide.

The same getter works with min and max:

cheapest = min(products, key=attrgetter("price"))
most_expensive = max(products, key=attrgetter("price"))

Nested attribute paths

A major advantage is dotted access. If an order has a customer and that customer has an address, you can retrieve the city with a string path.

get_city = attrgetter("customer.address.city")
city = get_city(order)

This is concise and readable in report builders, serializers, table renderers, and data pipelines. However, every intermediate object must exist. If customer is None, Python raises AttributeError. A custom function is better when optional values require defaults.

Retrieving multiple attributes

Passing multiple names returns a tuple.

key = attrgetter("stock", "name")
ordered = sorted(products, key=key)

Python compares tuples from left to right, so records are ordered by stock first and name second. Stable sorting makes this behavior predictable. Multiple getters are useful in reports and deterministic exports.

Grouping with itertools.groupby

attrgetter pairs naturally with itertools.groupby. The data must first be sorted by the same key because groupby combines consecutive equal keys.

from itertools import groupby
from operator import attrgetter

ordered = sorted(products, key=attrgetter("stock"))
for stock, group in groupby(ordered, key=attrgetter("stock")):
    print(stock, list(group))

See the introduction to itertools for the lazy iterator model behind this pattern.

attrgetter versus itemgetter

Use attrgetter for object attributes such as user.name. Use itemgetter for indexes or mapping keys such as user["name"].

from operator import attrgetter, itemgetter
attrgetter("name")(user)
itemgetter("name")({"name": "Ana"})

methodcaller, another helper from operator, creates a callable that invokes a method. For example, methodcaller("strip") can normalize strings. Choosing the helper that matches the data contract makes code easier to understand.

When a lambda is better

attrgetter is designed for direct attribute retrieval. If the key performs arithmetic, normalization, validation, fallback handling, or conditional logic, use a lambda or named function.

ordered = sorted(products, key=lambda p: p.price * (1 - p.discount))

Named functions are preferable when logic deserves tests or documentation.

def safe_city(order):
    customer = getattr(order, "customer", None)
    address = getattr(customer, "address", None)
    return getattr(address, "city", "")

The built-in getattr accepts a default value. attrgetter does not. Review the Python functions guide for strategies to keep key functions focused.

Reusing getters

A getter can be stored once and reused across operations.

by_created_at = attrgetter("created_at")
ordered = sorted(records, key=by_created_at)
latest = max(records, key=by_created_at)

This avoids repeated strings and centralizes the rule. In larger applications, reusable getters may belong near models, report definitions, or query adapters.

Performance considerations

attrgetter may be slightly faster than an equivalent lambda in some workloads because it is implemented as an optimized standard-library helper. Still, benchmark before treating that difference as important. Readability and correctness matter more in most business applications. For systematic measurements, use the techniques described in the timeit guide.

Properties and descriptors

attrgetter performs normal Python attribute access. Therefore it can execute properties, descriptors, or custom __getattr__ logic. If a property performs database access, network calls, or expensive computation, sorting may evaluate it many times. Key functions should ideally be deterministic, inexpensive, and free of side effects.

Do not use attrgetter as static introspection. The inspect module provides specialized tools when descriptors must not run.

Error handling

An incorrect name raises AttributeError. Validate external configuration before creating getters from user-provided strings. If a set of fields is configurable, maintain an allowlist rather than accepting arbitrary paths. Tests should cover missing attributes, None intermediate values, and mixed object types.

Practical design guidelines

Use attrgetter when the operation is only attribute retrieval. Prefer a named getter variable when it is reused. Keep sort and group keys consistent. Avoid expensive properties in keys. Use tuples for deterministic multi-field ordering. Choose a regular function for fallbacks or business rules. Document dotted paths when object relationships are not obvious.

For authoritative details, consult the official operator.attrgetter documentation and the official Python sorting HOWTO.

Conclusion

Python operator.attrgetter is a small but valuable tool for declarative object processing. It handles simple attributes, nested paths, and multiple fields while integrating cleanly with sorting, selection, and grouping functions. It should not replace every lambda. Instead, use it where attribute lookup is the entire rule, and switch to an explicit function when validation, transformation, defaults, or side effects enter the picture.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Asynchronous programming with Python asyncio.Runner
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    asyncio.Runner: Reuse the Event Loop Safely

    Learn Python asyncio.Runner to reuse an event loop, control context, signals, debug mode, cancellation, and safe asynchronous shutdown.

    Ler mais

    Tempo de leitura: 6 minutos
    02/09/2026
    Binary data compression with Zstandard in Python
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    compression.zstd: Zstandard Streams and Dictionaries

    Learn Python compression.zstd for Zstandard compression, streaming, dictionaries, safe limits, testing, and production workflows.

    Ler mais

    Tempo de leitura: 6 minutos
    01/09/2026
    Python application packaged as an executable zipapp archive
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python zipapp: Build Executable Apps

    Learn Python zipapp to package applications as executable pyz archives, include dependencies, and distribute tools safely.

    Ler mais

    Tempo de leitura: 5 minutos
    01/09/2026
    Python code used to compose functions with functools.Placeholder
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    functools.Placeholder: Positional Gaps in partial

    Learn Python functools.Placeholder to leave positional gaps in partial functions and build clearer reusable functional APIs.

    Ler mais

    Tempo de leitura: 5 minutos
    31/08/2026
    Developer coding and analyzing data with Python
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    itertools.pairwise: Compare Neighboring Values

    Learn Python itertools.pairwise to compare neighboring values, detect transitions, calculate deltas, and build clear lazy pipelines.

    Ler mais

    Tempo de leitura: 4 minutos
    31/08/2026
    A person typing on a laptop with a Python programming book visible, capturing technology and learning.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python types.new_class: Dynamic Classes

    Learn Python types.new_class for dynamic classes with metaclasses, prepared namespaces, inheritance, metadata, and safe factories.

    Ler mais

    Tempo de leitura: 3 minutos
    30/08/2026