23

I've only seen examples for setting the __repr__ method in class definitions. Is it possible to change the __repr__ for functions either in their definitions or after defining them?

I've attempted without success...

>>> def f():
    pass
>>> f
<function f at 0x1026730c8>
>>> f.__repr__ = lambda: '<New repr>'
>>> f
<function __main__.f>
beardc
  • 20,283
  • 17
  • 76
  • 94
  • While decorators are fun, it seems that the easier answer is to not make a function in the first place and instead make a class with `__repr__` and `__call__` methods directly, which can then be called as if it were a function. – GKFX Jun 28 '21 at 16:48

4 Answers4

29

Yes, if you're willing to forgo the function actually being a function.

First, define a class for our new type:

import functools
class reprwrapper(object):
    def __init__(self, repr, func):
        self._repr = repr
        self._func = func
        functools.update_wrapper(self, func)
    def __call__(self, *args, **kw):
        return self._func(*args, **kw)
    def __repr__(self):
        return self._repr(self._func)

Add in a decorator function:

def withrepr(reprfun):
    def _wrap(func):
        return reprwrapper(reprfun, func)
    return _wrap

And now we can define the repr along with the function:

@withrepr(lambda x: "<Func: %s>" % x.__name__)
def mul42(y):
    return y*42

Now repr(mul42) produces '<Func: mul42>'

kenm
  • 23,127
  • 2
  • 43
  • 62
  • 4
    Please use `functools.wraps` for decorators to update the name and the docstring of decorated functions. – schlamar Jun 04 '12 at 06:50
  • 1
    The problem is that `print mul42.__name__` would raise an AttributeError which is not expected for a function. So it would be: `return wraps(func)(reprwrapper(reprfun, func))` to fix this. – schlamar Jun 04 '12 at 06:56
  • 1
    @ms4py In this case I think `update_wrapper` is slightly more appropriate/direct. I've modified the wrapper class so that it does this in its constructor. This way the update happens even if you use the class directly instead of using the `withrepr` decorator. – kenm Jun 04 '12 at 12:42
9

No, because repr(f) is done as type(f).__repr__(f) instead.

JBernardo
  • 32,262
  • 10
  • 90
  • 115
5

In order to do that, you'd need to change the __repr__ function for the given class, which in this case is the built-in function class (types.FunctionType). Since in Python you cannot edit built-in classes, only subclass them, you cannot.

However, there are two approaches you could follow:

  1. Wrap some functions as kwatford suggested
  2. Create your own representation protocol with your own repr function. For example, you could define a myrepr function that looks for __myrepr__ methods first, which you cannot add to the function class but you can add it to individual function objects as you suggest (as well as your custom classes and objects), then defaults to repr if __myrepr__ is not found. A possible implementation for this would be:

    def myrepr(x):
      try:
        x.__myrepr__
      except AttributeError:
        return repr(x)
      else:
        return x.__myrepr__()
    

    Then you could define __myrepr__ methods and use the myrepr function. Alternatively, you could also do __builtins__.repr = myrepr to make your function the default repr and keep using repr. This approach would end up doing exactly what you want, though editing __builtins__ may not always be desirable.

Sean Allred
  • 3,558
  • 3
  • 32
  • 71
Miguel Pérez
  • 506
  • 3
  • 7
  • The custom repr protocol is interesting, however it's a bit more complex, since you also need to consider nested objects inside lists, dictionaries or similar. – mara004 Sep 12 '21 at 21:19
1

This appears to be difficult. Kwatford's approach only solves this problem partially since it does not work for functions in classes, becuase self would be treated like a positional argument, as explained in Decorating Python class methods - how do I pass the instance to the decorator? - However, the solution for that question is not applicable to this case, unfortunately, as using __get__() and functools.partial would override the custom __repr__().

mara004
  • 1,435
  • 11
  • 24