Can/should __str__() have *args and **kwargs?

In the codebase I’m working on, several classes define their __str__ method as def __str__(self, *args, **kwargs). As far as I can tell, they don’t seem to actually do anything with the extra arguments, but it got me wondering why this pattern is so pervasive. Was there ever a time that this was useful?

There has never been a time that __str__ has taken arbitrary arguments.

The special “dunder” (Double UNDERscore) method __str__ gets called when you try to convert an object into a string, e.g. using str(obj). There are no additional arguments accepted by str and the interpreter never passes additional arguments to __str__, so unless their code is directly calling the dunder method (a minor no-no), the extra *args, **kwargs are pointless.

1 Like

By J-M0 via Discussions on Python.org at 12May2022 19:14:

In the codebase I’m working on, several classes define their __str__
method as def __str__(self, *args, **kwargs). As far as I can tell,
they don’t seem to actually do anything with the extra arguments, but
it got me wondering why this pattern is so pervasive. Was there ever a
time that this was useful?

Do they define it in terms of a superclass’s __str__ maybe? If your
overriding a method from a superclass a common pattern is:

def x(self, *a, **kw):
    ... do something special involving calling super().x(*a,**kw) ...

Which calls the superclass x() method with whatever arguments were
supplied, then does something specific to the subclass with the result.

But if their __str__ is not using the arguments, I’d take it as a sign
that someone is blindly following a pattern rather than doing something
sensible. Unused arguments are usually a sign of a potential bug, and
various lint programs will warn about that.

Particularly with dunder methods like __str__, whose parameters are
usually very rigid because they’re called indirectly via things with no
scope for passing arbitrary parameters, I thnk this is almost never
useful. You almost never call a dunder method directly - they’re meant
to express mechanism for other things which don’t look so much like a
function call.

Even if the dunder method is a thin wrapper for a complex function:

def funky_format(self, formating_args_here....)
    ... return self as a string in many ways ...

def __str__(self):
    return self.funky_format(args for use in str here ....)

I’d expose the complex function directly as above, and leave __str__
with no arguments. Maybe its call to funky_format might match the
defaults for that function and therefore have no arguments:

def __str__(self):
    return self.funky_format()

but I would still have __str__ take no arguments, and funky_format
available for the other uses, whatever they might be.

Cheers,
Cameron Simpson cs@cskk.id.au

1 Like