echo.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # echo.py: Tracing function calls using Python decorators.
  4. #
  5. # Written by Thomas Guest <tag@wordaligned.org>
  6. # Please see http://wordaligned.org/articles/echo
  7. #
  8. # Place into the public domain.
  9. """ Echo calls made to functions and methods in a module.
  10. "Echoing" a function call means printing out the name of the function
  11. and the values of its arguments before making the call (which is more
  12. commonly referred to as "tracing", but Python already has a trace module).
  13. Example: to echo calls made to functions in "my_module" do:
  14. import echo
  15. import my_module
  16. echo.echo_module(my_module)
  17. Example: to echo calls made to functions in "my_module.my_class" do:
  18. echo.echo_class(my_module.my_class)
  19. Alternatively, echo.echo can be used to decorate functions. Calls to the
  20. decorated function will be echoed.
  21. Example:
  22. @echo.echo
  23. def my_function(args):
  24. pass
  25. """
  26. import inspect
  27. import sys
  28. def name(item):
  29. " Return an item's name. "
  30. return item.__name__
  31. def is_classmethod(instancemethod, klass):
  32. " Determine if an instancemethod is a classmethod. "
  33. return inspect.ismethod(instancemethod) and instancemethod.__self__ is klass
  34. def is_static_method(method, klass):
  35. """Returns True if method is an instance method of klass."""
  36. for c in klass.mro():
  37. if name(method) in c.__dict__:
  38. return isinstance(c.__dict__[name(method)], staticmethod)
  39. else:
  40. return False
  41. def is_class_private_name(name):
  42. " Determine if a name is a class private name. "
  43. # Exclude system defined names such as __init__, __add__ etc
  44. return name.startswith("__") and not name.endswith("__")
  45. def method_name(method):
  46. """ Return a method's name.
  47. This function returns the name the method is accessed by from
  48. outside the class (i.e. it prefixes "private" methods appropriately).
  49. """
  50. mname = name(method)
  51. if is_class_private_name(mname):
  52. mname = "_%s%s" % (name(method.__self__.__class__), mname)
  53. return mname
  54. def format_arg_value(arg_val):
  55. """ Return a string representing a (name, value) pair.
  56. >>> format_arg_value(('x', (1, 2, 3)))
  57. 'x=(1, 2, 3)'
  58. """
  59. arg, val = arg_val
  60. return "%s=%r" % (arg, val)
  61. def echo(fn, write=sys.stdout.write):
  62. """ Echo calls to a function.
  63. Returns a decorated version of the input function which "echoes" calls
  64. made to it by writing out the function's name and the arguments it was
  65. called with.
  66. """
  67. import functools
  68. # Unpack function's arg count, arg names, arg defaults
  69. code = fn.__code__
  70. argcount = code.co_argcount
  71. argnames = code.co_varnames[:argcount]
  72. fn_defaults = fn.__defaults__ or list()
  73. argdefs = dict(list(zip(argnames[-len(fn_defaults):], fn_defaults)))
  74. @functools.wraps(fn)
  75. def wrapped(*v, **k):
  76. # Collect function arguments by chaining together positional,
  77. # defaulted, extra positional and keyword arguments.
  78. positional = list(map(format_arg_value, list(zip(argnames, v))))
  79. defaulted = [format_arg_value((a, argdefs[a]))
  80. for a in argnames[len(v):] if a not in k]
  81. nameless = list(map(repr, v[argcount:]))
  82. keyword = list(map(format_arg_value, list(k.items())))
  83. args = positional + defaulted + nameless + keyword
  84. write("%s(%s)\n" % (name(fn), ", ".join(args)))
  85. return fn(*v, **k)
  86. return wrapped
  87. def echo_instancemethod(klass, method, write=sys.stdout.write):
  88. """ Change an instancemethod so that calls to it are echoed.
  89. Replacing a classmethod is a little more tricky.
  90. See: http://www.python.org/doc/current/ref/types.html
  91. """
  92. mname = method_name(method)
  93. never_echo = "__str__", "__repr__", # Avoid recursion printing method calls
  94. if mname in never_echo:
  95. pass
  96. elif is_classmethod(method, klass):
  97. setattr(klass, mname, classmethod(echo(method.__func__, write)))
  98. else:
  99. setattr(klass, mname, echo(method, write))
  100. def echo_class(klass, write=sys.stdout.write):
  101. """ Echo calls to class methods and static functions
  102. """
  103. for _, method in inspect.getmembers(klass, inspect.ismethod):
  104. # In python 3 only class methods are returned here, but in python2 instance methods are too.
  105. echo_instancemethod(klass, method, write)
  106. for _, fn in inspect.getmembers(klass, inspect.isfunction):
  107. if is_static_method(fn, klass):
  108. setattr(klass, name(fn), staticmethod(echo(fn, write)))
  109. else:
  110. # It's not a class or a static method, so it must be an instance method.
  111. # This should only be called in python 3, because in python 3 instance methods are considered functions.
  112. echo_instancemethod(klass, fn, write)
  113. def echo_module(mod, write=sys.stdout.write):
  114. """ Echo calls to functions and methods in a module.
  115. """
  116. for fname, fn in inspect.getmembers(mod, inspect.isfunction):
  117. setattr(mod, fname, echo(fn, write))
  118. for _, klass in inspect.getmembers(mod, inspect.isclass):
  119. echo_class(klass, write)
  120. if __name__ == "__main__":
  121. import doctest
  122. optionflags = doctest.ELLIPSIS
  123. doctest.testfile('echoexample.txt', optionflags=optionflags)
  124. doctest.testmod(optionflags=optionflags)