Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

How should I pass a class as an attribute to another class with attrs?

So, I just stumbled upon a hurdle concerning the use of attrs, which is quite new to me (I guess this also applies to dataclasses?). I have two classes, one I want to use as an attribute for another. This is how I would do this with regular classes:

class Address:
    def __init__(self) -> None:
        self.street = None

class Person:
    def __init__(self, name) -> None:
        self.name = name
        self.address = Address()

Now with attrs, I tried to do the following:

from attrs import define

@define
class Address:
    street: str | None = None

@define
class Person:
    name: str
    self.address = Address()

Now if I try the following, I don’t get the same result for a class and a dataclass, for which the reason wasn’t obvious to me at first:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

person_1 = Person("Joe")
person_2 = Person("Jane")

person_1.address.street = "street"
person_2.address.street = "other_street"

print(person_1.address.street)

I would expect the output to be "street", which is what happens with a regular class. But with attrs, the output is "other_street". I then compared the hashes of person_1.address and person_2.address, and voila, they are the same.

After some thinking this is logical, with attrs I instantiate Address immediately, so everyone gets the same instance of Address, with regular classes I only instantiate them when I instantiate the parent class.

Now, there is a fix available with attrs:

from attrs import define, field

@define
class Address:
    street: str | None = None

@define
class Person:
    name: str
    address: Address = field(init=False)

    def __attrs_post_init__(self):
        self.address = Address()

But this seems really cumbersome to implement every time. Is there a nice solution to this? One way would be to put the instantiation of Address outside of the class like this:

address_1 = Address()
person_1 = Person("Joe", address)

But my issue with that is, that often I want to instantiate the class in an empty state (for example to seperate input from computed values), and this way adds an extra step to instantiation which I need to remember.

So in conclusion: In this case, attrs, dataclass, pydantic etc. blur the line between what belongs to the class and what belongs to the instance, and in my case that led to an hour of "wtf happened here". So back to normal classes? I really like the default and validation possibilities of attrs though. Or is there a best practice way to handle this kind of setup?

>Solution :

You can use the factory argument to specify a callable that is called to return a new instance for the field during instantiation:

from attrs import define, field

@define
class Address:
    street: str | None = None

@define
class Person:
    name: str
    address: Address = field(factory=Address)
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading