How to set a repr for a function itself?

Viewed 625

__repr__ is used to return a string representation of an object, but in Python a function is also an object itself, and can have attributes.

How do I set the __repr__ of a function?

I see here that an attribute can be set for a function outside the function, but typically one sets a __repr__ within the object definition itself, so I'd like to set the repr within the function definition itself.


My use case is that I am using tenacity to retry a networking function with exponential backoff, and I want to log the (informative) name of the function I have called last.

retry_mysql_exception_types = (InterfaceError, OperationalError, TimeoutError, ConnectionResetError)


def return_last_retry_outcome(retry_state):
    """return the result of the last call attempt"""
    return retry_state.outcome.result()


def my_before_sleep(retry_state):
    print("Retrying {}: attempt {} ended with: {}\n".format(retry_state.fn, retry_state.attempt_number, retry_state.outcome))


@tenacity.retry(wait=tenacity.wait_random_exponential(multiplier=1, max=1200),
                stop=tenacity.stop_after_attempt(30),
                retry=tenacity.retry_if_exception_type(retry_mysql_exception_types),
                retry_error_callback=return_last_retry_outcome,
                before_sleep=my_before_sleep)
def connect_with_retries(my_database_config):
    connection = mysql.connector.connect(**my_database_config)
    return connection

Currently retry_state.fn displays something like <function <lambda> at 0x1100f6ee0> like @chepner says, but I'd like to add more information to it.

5 Answers

You could use a decorator that returns a class with the __call__ and __repr__ set:

class CustomReprFunc:

    def __init__(self, f, custom_repr):
        self.f = f
        self.custom_repr = custom_repr

    def __call__(self, *args, **kwargs):
        return self.f(*args, **kwargs)

    def __repr__(self):
        return self.custom_repr(self.f)


def set_repr(custom_repr):
    def set_repr_decorator(f):
        return CustomReprFunc(f, custom_repr)
    return set_repr_decorator


@set_repr(lambda f: f.__name__)
def func(a):
    return a


print(repr(func))

It's already set.

>>> repr(lambda x:x)
'<function <lambda> at 0x1100f6ee0>'

The problem is that the function type is immutable, so you can't just assign a new function to function.__repr__, and you also can't create a subtype of function in order to override __repr__. (Not that creating instances of the subclass would be easy, even if it were possible to define it.)

You can't do this for actual functions; the function type is immutable, and already defines a __repr__, and __repr__ is looked up on the type, not the instance, so changing __repr__ on a given function doesn't change behavior.

While probably not useful in this case, you can make your own callable class (analogous to C++ functors), and those can define their own __repr__. For example:

class myfunction:
    @staticmethod   # Avoids need to receive unused self
    def __call__(your, args, here):
        ... do stuff and return as if it were a function ...

    @classmethod    # Know about class, but again, instance is useless
    def __repr__(cls):
        return f'{cls.__name__}(a, b, c)'

which you could convert to a singleton instance of the class (making it equivalent to a plain function in how you use it) at the end by just doing:

myfunction = myfunction()

to replace the class with a single instance of the class.

Note: In real code, I'd almost certainly just change where I'm printing it to print in a more useful way without modifying the function. This doesn't have much overhead over a plain function or a wrapped plain function (since we put the function itself in __call__ rather than wrapping, making it faster, but requiring a separate class for each "friendly repr function"), but it's just not the job of the function to decide how to represent itself in a human-friendly way; that's your job, based on the situation.

I think a custom decorator could help:

import functools


class reprable:
    """Decorates a function with a repr method.

    Example:
        >>> @reprable
        ... def foo():
        ...     '''Does something cool.'''
        ...     return 4
        ...
        >>> foo()
        4
        >>> foo.__name__
        'foo'
        >>> foo.__doc__
        'Does something cool.'
        >>> repr(foo)
        'foo: Does something cool.'
        >>> type(foo)
        <class '__main__.reprable'>
    """

    def __init__(self, wrapped):
        self._wrapped = wrapped
        functools.update_wrapper(self, wrapped)

    def __call__(self, *args, **kwargs):
        return self._wrapped(*args, **kwargs)

    def __repr__(self):
        return f'{self._wrapped.__name__}: {self._wrapped.__doc__}'

Demo: http://tpcg.io/uTbSDepz.

You can change retry_state.fn to retry_state.__name__. I use many decorators like this. If you add a decorator, it will be called each time a function of interest is called.

def display_function(func):
    """ This decorator prints before and after running """

    @functools.wraps(func)
    def function_wrapper(*args, **kwargs):
        print(f'\nNow: Calling {func.__name__}.')
        entity = func(*args, **kwargs)
        print(f'Done: Calling {func.__name__}.\n')
        return entity

    return function_wrapper

Additionally, the retrying module in python allows you to do some of what you're doing by default. I often use a decorator:

import retrying
@retrying.retry(wait_exponential_multiplier=1000, wait_exponential_max=10000)
Related