2025, Sep 21 09:00

Python attrs default field values done right: @field.default vs Factory(takes_self=True), avoid NameError and order pitfalls

For Python attrs, learn the right pattern for default fields: @field.default or Factory(takes_self=True), keep order correct, avoid NameError, AttributeError

When using attrs to compute default field values, a common pitfall is referencing a factory function before it exists. That leads to a NameError at import time and quickly spirals into confusion about definition order, @field.default, and Factory(takes_self=True). Let’s distill the moving parts and show a clean, reliable pattern.

Minimal working pattern

The lean approach is to attach a default provider directly to the field via the field’s .default decorator. The function is a method, receives self, and can rely on attributes that were already initialized on the instance.

from attrs import define, field

@define
class DataBox:
    label = field(init=False)

    @label.default
    def _label_maker(self):
        return "standard name"  # plug in any computation you need
print(DataBox())
# DataBox(label='standard name')

Because the default provider receives self, it can derive values from previously initialized fields:

from attrs import define, field

@define
class DataBox:
    x = field()
    y = field()
    label = field(init=False)

    @label.default
    def _label_maker(self):
        return f"standard name {self.x * self.y}"
print(DataBox(x=7, y=6))
# DataBox(x=7, y=6, label='standard name 42')

What actually goes wrong

The NameError in the original scenario happens because a function is passed to Factory before that function is defined. In Python, definition order matters. If you hand Factory a symbol that doesn’t exist yet, the module import fails immediately.

Definition order also matters for attribute access inside the default provider. If a field tries to use self.some_attr that hasn’t been set yet, you’ll hit an AttributeError at initialization.

from attrs import define, field

@define
class DataBox:
    label = field(init=False)
    x = field()
    y = field()

    @label.default
    def _label_maker(self):
        return f"standard name {self.x * self.y}"
print(DataBox(x=7, y=6))
# AttributeError: 'DataBox' object has no attribute 'x'

Here label is declared before x and y, so when the default runs it can’t read those attributes.

Solution with @field.default

Attach the default provider directly to the field and keep fields that your provider depends on above it. The method will receive self and can read any values that have already been set on the instance. This is compact, readable, and prevents premature references to names that don’t exist yet.

from attrs import define, field

@define
class DataBox:
    x = field()
    y = field()
    label = field(init=False)

    @label.default
    def _label_maker(self):
        return f"standard name {self.x * self.y}"

Alternative using Factory(takes_self=True)

If you prefer Factory, define the provider method first, then reference it in the field’s default with takes_self=True. This avoids the NameError by ensuring the function exists before it’s used and still gives you access to self.

from attrs import define, field, Factory

@define
class DataBox:
    def _label_maker(self):
        return "standard name"

    label = field(default=Factory(_label_maker, takes_self=True), init=False)

print(DataBox())
# DataBox(label='standard name')

And the same pattern when deriving from other attributes, provided those fields appear before the dependent field:

from attrs import define, field, Factory

@define
class DataBox:
    def _label_maker(self):
        return f"standard name {self.x * self.y}"

    x = field()
    y = field()
    label = field(default=Factory(_label_maker, takes_self=True), init=False)

print(DataBox(x=7, y=6))
# DataBox(x=7, y=6, label='standard name 42')

If you invert the order, the dependent method won’t see the attributes yet and you’ll get an AttributeError:

from attrs import define, field, Factory

@define
class DataBox:
    def _label_maker(self):
        return f"standard name {self.x * self.y}"

    label = field(default=Factory(_label_maker, takes_self=True), init=False)
    x = field()
    y = field()

print(DataBox(x=7, y=6))
# AttributeError: 'DataBox' object has no attribute 'x'

Why this matters

In attrs-heavy codebases, defaults frequently depend on other fields. Understanding when names exist, when values are available on self, and how definition order influences both helps avoid brittle initialization code. It also improves readability: future readers can see at a glance which fields feed into a default and how the value is computed.

Wrap-up

Use the field’s .default decorator when you can. It’s explicit, keeps the logic close to the field, and gives you self so you can compose defaults from previously initialized attributes. If you prefer Factory, define the provider before using it and pass takes_self=True. In both styles, keep dependent fields above the one that consumes them. Respecting definition order will save you from NameError and AttributeError at the worst possible time—module import and object creation.

The article is based on a question from StackOverflow by Daemon Painter and an answer by simon.