The echo module "echoes" calls to functions by tracing the arguments they're called with. It also supports echoing classes and modules by tracing all function calls made to them. Here's how. >>> import echo Let's define a function and echo calls to it: >>> def f(x, y, z): ... pass >>> f = echo.echo(f) >>> f(1, 2, 'three') f(x=1, y=2, z='three') We can also use decorator syntax: >>> @echo.echo ... def f(x, y, z): ... pass ... >>> f(1, 2, 'three') f(x=1, y=2, z='three') The decorated function maintain the undecorated function's attributes as far as possible: >>> def f(): ... " I'm f, don't change my docstring " ... pass ... >>> echo.echo(f).__doc__ " I'm f, don't change my docstring " Default arguments are echoed: >>> @echo.echo ... def fn_with_defaults(x=1, y=None): ... pass ... >>> fn_with_defaults() fn_with_defaults(x=1, y=None) Arbitrary nameless arguments are echoed: >>> @echo.echo ... def fn_with_nameless_args(*args): ... pass ... >>> fn_with_nameless_args(1, 2, 3) fn_with_nameless_args(1, 2, 3) >>> fn_with_nameless_args("abc", (1, 2, 3)) fn_with_nameless_args('abc', (1, 2, 3)) And so are keyword arguments (though note that the order of the output "arg=value" pairs is undefined). >>> @echo.echo ... def fn_with_keyword_args(**k): ... pass ... >>> fn_with_keyword_args(breakfast="spam") fn_with_keyword_args(breakfast='spam') >>> fn_with_keyword_args(breakfast='spam', lunch='spam', dinner='spam') fn_with_keyword_args(...) You can mix default positional, arbitrary and keyword arguments: >>> @echo.echo ... def full_monty(x, y, z='muesli', *v, **k): ... pass ... >>> full_monty('spam', 'eggs', extra='more spam') full_monty(x='spam', y='eggs', z='muesli', extra='more spam') >>> full_monty('spam', 'eggs', 'more spam', extra='even more spam') full_monty(x='spam', y='eggs', z='more spam', extra='even more spam') You can echo functions in a class by decorating them. >>> class Example(object): ... @echo.echo ... def __init__(self): pass ... @echo.echo ... def m(self): pass ... >>> ex = Example() __init__(self=) >>> ex.m() m(self=) This works equally well on classmethods and staticmethods, as well as on classic classes >>> class AnotherExample: ... @classmethod ... @echo.echo ... def cm(klass): pass ... @staticmethod ... @echo.echo ... def sm(): pass ... >>> AnotherExample.cm() cm(klass=) >>> AnotherExample.sm() sm() >>> another_ex = AnotherExample() >>> another_ex.cm() cm(klass=) >>> another_ex.sm() sm() Alternatively, don't decorate the methods you want to echo up front, retrospectively decorate the whole class. >>> class YetAnotherExample(object): ... def __init__(self): pass ... def m(self, x, y): pass ... @classmethod ... def cm(klass, x, y): pass ... @staticmethod ... def sm(x, y): pass ... >>> echo.echo_class(YetAnotherExample) >>> y = YetAnotherExample() __init__(self=) >>> y.m('echo', 'echo') m(self=, x='echo', y='echo') >>> y.cm('echo', 'echo') cm(klass=, x='echo', y='echo') >>> y.sm('echo', 'echo') sm(x='echo', y='echo') Private methods are echoed as well. >>> class Privates(object): ... def __myob(self): pass ... def do_something(self): self.__myob() ... >>> Privates().do_something() >>> echo.echo_class(Privates) >>> Privates().do_something() do_something(self=) __myob(self=)