4 ms·
> Positional arguments means a caller can use MyDataClass(1, 'foo', False), and if you remove/reorder any of these arguments, you’ll break those callers unexpec
by joshdavham 1y ago
> Positional arguments means a caller can use MyDataClass(1, 'foo', False), and if you remove/reorder any of these arguments, you’ll break those callers unexpectedly. By forcing callers to use MyDataClass(x=1, y='foo', z=False), you remove this risk.
This is an awesome way to prevent future breaking changes!
...but unfortunately, adding this to an existing project would also likely result in breakings changes haha
- chipx86 1y agoThat's always the challenge when iterating on interfaces that other people depend on. What we do is go through a deprecation phase. Our process is: * We provide compatibility with the old signature for 2 major releases. * We document the change and the timeline clearly in the docstring. * The function gets decorated with a helper that checks the call, and if any keyword-only arguments are provided as positional, it warns and converts them to keyword-only. * After 2 major releases, we move fully to the new signature. We buit a Python library called housekeeping (https://github.com/beanbaginc/housekeeping https://github.com/beanbaginc/housekeeping) to help with this. One of the things it contains is a decorator called `@deprecate_non_keyword_only_args`, which takes a deprecation warning class and a function using the signature we're moving to. That decorator handles the check logic and generates a suitable, consistent deprecation message. That normally looks like: @deprecate_non_keyword_only_args(MyDeprecationWarning) def my_func(*, a, b, c): ... But this is a bit more tricky with dataclasses, since `__init__()` is generated automatically. Fortunately, it can be patched after the fact. A bit less clean, but doable. So here's how we'd handle this case with dataclasses: from dataclasses import dataclass from housekeeping import BaseRemovedInWarning, deprecate_non_keyword_only_args class RemovedInMyProject20Warning(BaseRemovedInWarning): product = 'MyProject' version = '2.0' @dataclass(kw_only=True) class MyDataclass: a: int b: int c: str MyDataclass.__init__ = deprecate_non_keyword_only_args( RemovedInMyProject20Warning )(MyDataclass.__init__) Call it with some positional arguments: dc = MyDataclass(1, 2, c='hi') and you'd get: testdataclass.py:26: RemovedInMyProject20Warning: Positional arguments `a`, `b` must be passed as keyword arguments when calling `__main__.MyDataclass.__init__()`. Passing as positional arguments will be required in MyProject 2.0. dc = MyDataclass(1, 2, c='hi') We'll probably add explicit dataclass support to this soon, since we're starting to move to kw_only=True for dataclasses.
- deleted 1y ago[deleted]
- Lutger 1y agoEven better is ... just not breaking the interface, leave the code be. Add parameters at the end. Or just add a new function. Or even a new module if things get too messy for your liking. Just leave the old users in peace. If you are worried about breaking stuff, it means people are using the function you wrote and find value in it. Why change it at all? I think it is very rare that the reasons for breaking are so compelling that they are worth the trouble.
- ziml77 1y agoBut the problem here is that with a dataclass you never explicitly defined any parameters. It's the dataclass annotation that defined them for the constructor based on the order of the fields. So, yes the solution is to never re-order the fields and only add new ones to the end, but this can be a surprising requirement because normally the order of fields in a class doesn't matter to users of the class.