
On Tue, 3 May 2022 at 03:04, Steven D'Aprano <steve@pearwood.info> wrote:
On Mon, May 02, 2022 at 07:44:14PM +0100, Paul Moore wrote:
I have classes with 20+ parameters (packaging metadata). You can argue that a dataclass would be better, or some other form of refactoring, and you may actually be right. But it is a legitimate design for that use case.
Indeed. 20+ parameters is only a code smell, it's not *necessarily* wrong. Sometimes you just need lots of parameters, even if it is ugly.
For reference, open() only takes 8, so 20 is a pretty wiffy code smell, but it is what it is.
It's worth noting that dataclasses with lots of attributes by default generate constructors that require all of those as parameters. So it's a code smell yes, but by that logic so are dataclasses with many attributes (unless you write a bunch of custom code). Genuine question - what *is* a non-smelly way of writing a dataclass with 24 attributes? I've written about 20 variations on this particular class so far, and none of them feel "right" to me :-(
Of course the real problem is that you often don't want to *quite* assign the argument unchanged - `self.provides_extras = set(provides_extras or [])` or `self.requires_python = requires_python or specifiers.SpecifierSet()` are variations that break the whole "just assign the argument unchanged" pattern.
Indeed. Once we move out of that unchanged assignment pattern, we need to read more carefully rather than skim
self._spam = (spam or '').lower().strip()
but you can't replace that with auto assignment.
Precisely.
As a variation on the issue, which the @ syntax *wouldn't* solve, in classmethods for classes like this, I often find myself constructing dictionaries of arguments, copying multiple values from one dict to another, sometimes with the same sort of subtle variation as above:
@classmethod def from_other_args(cls, a, b, c, d): kw = {} kw["a"] = a kw["b"] = b kw["c"] = c kw["d"] = d return cls(**kw)
You may find it easier to make a copy of locals() and delete the parameters you don't want, rather than retype them all like that:
params = locals().copy() for name in ['cls', 'e', 'g']: del params[name] return cls(**params)
Again, in "real code", not all of these would be copied, or some would have defaults, etc. The pattern's the same, though - enough args arecopied to make the idea of marking them with an @ seem attractive.
But the @ proposal here won't help. If you mark them with @, won't they be auto-assigned onto cls?
Again, precisely. My point here is that the @ proposal is, in my experience, useful in far fewer situations than people are claiming. What *is* common (again in my experience) is variations on a pattern that can be described as "lots of repetitive copying of values from one location to another, possibly with minor modifications". Having a way of addressing the broader problem *might* be of sufficient use to be worth pursuing, and it might even be possible to do something useful in a library, not needing new syntax. On the other hand, the @ syntax as proposed *doesn't* address enough use cases (for me!) to be worthwhile, especially not if new syntax is needed rather than just something like a decorator. Paul