Search code examples
pythonpython-dataclassespython-class

Can you modify an object's field every time another field is modified?


I have a dataclass that looks like this

from dataclasses import dataclass, field

@dataclass
class Data:
    name: str | None = None
    file_friendly_name: str | None = field(default=None, init=False)

    def __post_init__(self):
        # If name, automatically create the file_friendly_name
        if self.name:
            self.file_friendly_name = "".join(
                i for i in self.name if i not in "/:*?<>|"
            )

If user passes name on instantiation, file_friendly_name is automatically created.

Is there a way to do it so that every time name is updated/changed, file_friendly_name also changes?

e.g.

data = Data()
data.name = 'foo/bar'
print(data.file_friendly_name) # want: 'foobar'

data = Data(name='foo/bar')
data.name = 'new?name'
print(data.file_friendly_name) # want: 'newname'

Update based on answers:

  1. I've tried setting _name: str and creating name using getters/setters. But I don't like how when you do print(Data()) it shows _name as an attribute. I'd like that not to happen.
  2. I like setting file_friendly_name as a property. But then you can't see that as an attribute when you do print(Data()). This is less of an issue but still not ideal.

Can it just show name and file_friendly_name as attributes when doing print(Data())?


Solution

  • I'd suggest defining file_friendly_name as @property instead.

    from dataclasses import dataclass, fields
    
    @dataclass
    class Data:
        name: str | None = None
        
        @property
        def file_friendly_name(self) -> str | None:
            if self.name is not None:
                return "".join(
                    i for i in self.name if i not in "\/:*?<>|"
                )
            else:
                return None
    
        def __repr__(self):
            fields_str = [f'{field.name}={getattr(self, field.name)!r}'
                          for field in fields(self)]
            fields_str.append(f'file_friendly_name={self.file_friendly_name}')
            fields_res = ', '.join(fields_str)
            return f'{type(self).__name__}({fields_res})'