
Dexter Hill wrote:
Steve Jorgensen wrote:
Would we want something more general that could deal with cases where the input does not have a 1-to-1 mapping to the field that differ only, perhaps, in type hint? What if we want 1 argument to initializes 2 properties or vice verse, etc.? That's definitely an improvement that could be made, although I think it would require a large amount of changes. I don't know if you had syntax in mind for it, or an easy way to represent it, but at least from what I understand you would probably a whole new function like `field`, but that handles just that functionality, otherwise it would add a lot of arguments to `field`. Steve Jorgensen wrote: In any case, having a new `InitFn` is worth digging into, I don't think it needs to have 2 arguments for type since the type annotation already covers 1 of those cases. I think it makes the most sense for the type annotation to apply to the property and the type of the argument to be provided either through an optional argument to `InitFn` or maybe that can be derived from the signature of the function that `InitFn` refers to. So the use case would be either this:
@dataclass class Foo: x: InitFn[str] = field(converter=chr)
where the field `x` has the type string, and the type for the `x` parameter in `__init__` would be derrived from `chr`, or optionally: ```py @dataclass class Foo: x: InitFn[str, int] = field(converter=chr) ``` where you can provide a second type argument that specifies the type parameter for `__init__`?
How about this variation? Use with `init_using` instead of `converter` as the name of the argument to field, allow either a callable or a method name to be supplied, and expect the custom init function to behave like `__post_init__` in that it assigns to properties rather than returning a converted value. That will allow it to initialize more than 1 property. Next, we can say that if the same callable object or the same method name is passed to `init_using`, then it is called only once. Finally, we say that the class' init argument(s) and their type hints are taken from the `init_using` target. ``` @dataclass class DocumentFile: filename: str = field(init_using='_init_name_and_ctype') content_type: str = field(init_using='_init_name_and_ctype') description: str | None = field(default=None) # In this case, the function takes a `file_name` argument which is the same # as one of the property names that it initializes, but it could take an argument # with a completely different name, and the class init would have that as its # an argument instead. def _init_name_and_ctype(self, filename: str | Path = '/tmp/example.txt') -> None: self.filename = str(filename) self.content_type = mimetypes.guess_type(filename) # Roughly translates to class DocumentFile: filename: str content_type: str description: str | None def __init__(self, filename: str | Path = '/tmp/example.txt', description: str | None = None): self.description = description self._init_name_and_ctype(filename) def _init_name_and_ctype(self, file_name: str | Path = '/tmp/example.txt') -> None: self.file_name = str(file_name) self.content_type = mimetypes.guess_type(file_name) ```