Skip to content

Possible lazy=True Feature? #573

@pymarv

Description

@pymarv

Hi Everyone,

First, thanks for the great tool -- I really love writing Python code with it!

I find myself doing things like this a lot when I want b and c to only run when called (and then only once with the result cached).

import attr
from cached_property import cached_property

@attr.s
class MyClass:
    a = attr.ib(
        validator=attr.validators.instance_of(int),
        converter=int,
    )

    @cached_property
    def b(self):
        return str(self.a) * 3

    @cached_property
    def c(self):
        return int(self.b) * 3

foo = MyClass(a='1')
print(f'foo.a: {foo.a}')
print(f'foo.b: {foo.b}')
print(f'foo.c: {foo.c}')

But then I have to "break out of attrs" and loose the other goodness it provides. I know I can do something like this:

import attr

@attr.s
class MyClass:
    a = attr.ib(
        validator=attr.validators.instance_of(int),
        converter=int,
    )
    b = attr.ib(
        validator=attr.validators.instance_of(str),
        default=attr.Factory(
            takes_self=True,
            factory=lambda self: str(self.a) * 3,
        )
    )
    c = attr.ib(
        validator=attr.validators.instance_of(int),
        default=attr.Factory(
            takes_self=True,
            factory=lambda self: int(self.b) * 3,
        )
    )

foo = MyClass(a='1')
print(f'foo.a: {foo.a}')
print(f'foo.b: {foo.b}')
print(f'foo.c: {foo.c}')

But then b and c get "run" or "built" every time, even if they aren't needed (and I'm obviously assuming in reality they do more work they my toy example here shows).

What I really want to do is something like:

import attr

@attr.s
class MyClass:
    a = attr.ib(
        validator=attr.validators.instance_of(int),
        converter=int,
    )
    b = attr.ib(
        validator=attr.validators.instance_of(str),
        lazy=True,
        default=attr.Factory(
            takes_self=True,
            factory=lambda self: str(self.a) * 3,
        )
    )
    c = attr.ib(
        validator=attr.validators.instance_of(int),
        lazy=True,
        default=attr.Factory(
            takes_self=True,
            factory=lambda self: int(self.b) * 3,
        )
    )

foo = MyClass(a='1')
print(f'foo.a: {foo.a}')
print(f'foo.b: {foo.b}')
print(f'foo.c: {foo.c}')

Or even better, something that doesn't require me to specify takes_self=True every time... maybe something like:

import attr

@attr.s
class MyClass:
    a = attr.ib(
        validator=attr.validators.instance_of(int),
        converter=int,
    )
    b = attr.ib(
        validator=attr.validators.instance_of(str),
        lazy=True,
        builder=attr.Builder(
            factory=lambda self: str(self.a) * 3,
        )
    )
    c = attr.ib(
        validator=attr.validators.instance_of(int),
        lazy=True,
        builder=attr.Builder(
            factory=lambda self: int(self.b) * 3,
        )
    )

foo = MyClass(a='1')
print(f'foo.a: {foo.a}')
print(f'foo.b: {foo.b}')
print(f'foo.c: {foo.c}')

Thoughts on if something like this would be possible? I haven't dug into guts of attrs yet enough to know if it's doable, but it seems like it would be a great enhancement if something like that is possible. I came not too long ago from a non-Python environment where we built classes almost exactly like I show in my last example above, and it's a super efficient and expressive way to do things.

Thanks!

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions