0

Hi I have a model that contain several items, now all of the items have a set of shared properties: id, name, parent. In addition some of this items are meant only to contain other items. Some are meant to connect to other items and so on. While trying to implement this I can think of doing inheritance but I can also think about doing composition (and have read a lot of comments saying to prefer this). So I can define a class with the basic properties whose main job is just to provide access and allow modification of them:

from attrs import define, Factory
@define
class BaseProperties:
  identifier: str
  name: str
  parent_id: Optional[str]

@define
class ConnectorItem:
  base_properties: BaseProperties
  connections: "List[ConnectorItems]" = Factory(list)

@define
class ContainerItem:
  base_properties: BaseProperties
  children: "List[Union[ConnectorItem, ContainerItem]]" = Factory(list)

now the thing is that when I want to access to the name or id of a Container item for example, I would need to do:

container_item.base_properties.identifier

or define a property inside the ContainerItem.

@property
def identifier(self):
  return self.base_properties.identifier

if i want to just use:

container_item.identifier

Now the first option feels like the user need to know about the internal implementation of my ContainerItem class and can lead to a lot of dot notation to access some of the members when the project grows.

The second option feels cumbersome when I would need to basically copy paste a bunch of setter and getter properties in each of the classes that contain the BaseProperties class.

Should I go with inheritance in this case instead of composition? The thing is that even if ContainerItem and ConnectorItem share the basic properties I wont be using them in the same context ever so i don't need polymorphism in this case just basic code re-usability i think.

What is a better way of implementing this case, knowing that this is a simplified version, in reality ConnectorItem might be composed from different classes to get different ConnectorItemTypes for example.

2
  • You should make ContainerItem a subclass of BaseProperties.
    – Barmar
    Commented May 3 at 16:17
  • So just inheritance in this case? For both of them I guess not only ContainerItem.
    – Al_
    Commented May 4 at 1:10

1 Answer 1

0

What you're doing here is strict specialization, the one good reason for code-sharing via subclassing. It isn't really an attrs question but a wider Python and software engineering topic.

I've blogged about this topic in the past where I tackle specifically this case: https://hynek.me/articles/python-subclassing-redux/ (your topic starts at "Type 3: Specialization" but I recommend reading the rest for context). If you prefer video, I gave a talk at PyCon US 2023 about it too: https://www.youtube.com/watch?v=k8MT5liCQ7g (topic starts at 37:33, same caveat).

To give one thing away: the problem you're trying to solve is even possible in we-don't-subclass-around-here Go and and they call it embedding.

4
  • I was feeling the same and then started encountering this prefer composition over inheritance thing everywhere. So I was wondering about it. Not sure why @Barmar added the attrs tag to the question, it was not on my original question. Thanks for the resources, I will check them. Edit: The video doesn't seem to be available anymore, maybe wrong link?
    – Al_
    Commented May 6 at 11:49
  • @Al_ yes sorry! I was removing the tracking garbage and cut off one char too many – please try again! :)
    – hynek
    Commented May 6 at 16:52
  • 1
    Thanks, is working now. Both the blog post and the video are very informative and what I was looking for. Btw I knew your name ringed a bell, I recently started using attrs and the reason I open this question was the line in the attrs docs saying "Subclassing is bad for you". Funny thing that you ended up answering. Maybe adding a link to the blogpost there could be useful for others in the same situation.
    – Al_
    Commented May 7 at 14:49
  • done 🤓
    – hynek
    Commented 2 days ago

Not the answer you're looking for? Browse other questions tagged or ask your own question.