Keyword Parameters¶
log_calls has many features, and thus many, mostly independent, keyword parameters (21 in release 0.3.2). This section covers most of them thoroughly, one at a time (though of course you can use multiple parameters in any call to the decorator):
- enabled
- args_sep
- log_args
- log_retval
- log_exit
- log_call_numbers
- log_elapsed
- indent
- name
- prefix
- file
- mute (also discusses the global mute switch
log_calls.mute
) - settings
- NO_DECO
- override
The remaining parameters are fully documented in later chapters, For completeness, they’re briefly introduced at the end of this chapter, together with links to their actual documentation.
What is a setting?¶
When log_calls decorates a callable (a function, method, property, ...), it “wraps” that callable in a function — the wrapper of the callable. Subsequently, calls to the decorated callable actually call the wrapper, which delegates to the original, in between its own pre- and post-processing. This is simply what decorators do.
log_calls gives the wrapper a few attributes pertaining to the wrapped callable, notably
log_calls_settings
, a dict-like object that contains the log_calls state of the callable.
The keys of log_calls_settings
are log_calls keyword parameters, such as enabled
and
log_retval
— in fact, most of the keyword parameters, though not all of them.
The settings of a decorated callable are the key/value pairs of its
log_calls_settings
object, which is an attribute of the callable’s wrapper.
The settings comprise the log_calls state of the callable.
Initially the value of a setting is the value passed to the log_calls decorator for
the corresponding keyword parameter, or the default value for that parameter if no
argument was supplied for it. log_calls_settings
can then be used to read and write
settings values.
log_calls_settings
is documented in The log_calls_settings attribute — the settings API.
Usage of “setting”
We also use the term “settings” to refer to the keys of log_calls_settings
,
as well as to its key/value pairs. For example,
“theindent
setting”,
or
“enabled
is a setting, butoverride
is not”.
This overloading shouldn’t cause any confusion.
The “settings”¶
The following keyword parameters are settings:
enabled
args_sep
log_args
log_retval
log_exit
log_call_numbers
log_elapsed
indent
prefix
file
mute
logger
loglevel
record_history
max_history
As described in the chapter Dynamic Control of Settings, all of a decorated callable’s
settings can be accessed through log_calls_settings
, and almost all can be changed
on the fly.
The non-settings¶
The other keyword parameters are not settings:
NO_DECO
settings
name
override
omit
only
These are directives to the decorator telling it how to initialize itself. Their initial values are not subsequently available via attributes of the wrapper, and cannot subsequently be changed.
enabled
(default: True
( == 1
) )¶
Every example of log_calls that we’ve seen so far has produced output,
as they have all used the default value True
of the enabled
parameter.
Passing enabled=False
to the decorator suppresses output:
>>> @log_calls(enabled=False)
... def f(a, b, c):
... pass
>>> f(1, 2, 3) # no output
This is not totally pointless!, because, as with almost all log_calls settings,
you can dynamically change the “enabled” state for a particular function or method.
(Later chapters Bulk (Re)Decoration, (Re)Decorating Imports and
Dynamic Control of Settings show ways to do so that could change this
enabled
setting.) The above decorates f
and sets its initial “enabled” state
to False
.
Note
The enabled
setting is in fact an int
. This can be used advantageously.
See the examples Using enabled as a level of verbosity and A metaclass example, which illustrate using different positive values to specify increasing levels of verbosity in log_calls-related output.
Bypass¶
If you supply a negative integer as the value of enabled
, that is interpreted as bypass: log_calls
immediately calls the decorated callable and returns its value. When the value
of enabled
is false (False
or 0
), the decorator performs a little more processing
than that before it delegates to the decorated callable (it increments the number of the call, for example),
though of course less than when enabled
is positive (e.g. True
).
args_sep
(default: ', '
)¶
The args_sep
parameter specifies the string used to separate arguments. If the string ends in
\n
(in particular, if sep
is '\n'
), additional whitespace is interspersed so that
arguments line up nicely:
>>> @log_calls(args_sep='\\n')
... def f(a, b, c, **kwargs):
... print(a + b + c)
>>> f(1, 2, 3, u='you')
f <== called by <module>
arguments:
a=1
b=2
c=3
**kwargs={'u': 'you'}
6
f ==> returning to <module>
log_args
(default: True
)¶
When true, as seen in all examples so far, arguments passed to the decorated callable
are written together with their values. If the callable’s signature contains positional
and/or keyword “varargs”, those are included if they’re nonempty. (These are conventionally named
*args
and **kwargs
, but log_calls will use the parameter names that actually appear in
the callable’s definition.) Any default values of keyword parameters with no corresponding argument
are also logged, on a separate line:
>>> @log_calls()
... def f_a(a, *args, something='that thing', **kwargs): pass
>>> f_a(1, 2, 3, foo='bar')
f_a <== called by <module>
arguments: a=1, *args=(2, 3), **kwargs={'foo': 'bar'}
defaults: something='that thing'
f_a ==> returning to <module>
Here, no argument information is logged at all:
>>> @log_calls(log_args=False)
... def f_b(a, *args, something='that thing', **kwargs): pass
>>> f_b(1, 2, 3, foo='bar')
f_b <== called by <module>
f_b ==> returning to <module>
If a callable has no parameters, log_calls won’t display any “arguments” section:
>>> @log_calls()
... def f(): pass
>>> f()
f <== called by <module>
f ==> returning to <module>
If a callable has parameters but is passed no arguments, log_calls
will display arguments: <none>
, plus any default values used:
>>> @log_calls()
... def ff(*args, **kwargs): pass
>>> ff()
ff <== called by <module>
arguments: <none>
ff ==> returning to <module>
>>> @log_calls()
... def fff(*args, kw='doh', **kwargs): pass
>>> fff()
fff <== called by <module>
arguments: <none>
defaults: kw='doh'
fff ==> returning to <module>
log_retval
(default: False
)¶
When this setting is true, values returned by a decorated callable are reported:
>>> @log_calls(log_retval=True)
... def f(a, b, c):
... return a + b + c
>>> _ = f(1, 2, 3)
f <== called by <module>
arguments: a=1, b=2, c=3
f return value: 6
f ==> returning to <module>
Note
By default, log_calls suppresses the return value of __init__
methods,
even when log_retval=True
has been passed to a decorator of the method’s
class. To override this, you’d have to decorate __init__
itself and supply
log_retval=True
. However, there’s no reason to: __init__
returns None
.
log_exit
(default: True
)¶
When false, this parameter suppresses the ... ==> returning to ...
line
that indicates the callable’s return to its caller:
>>> @log_calls(log_exit=False)
... def f(a, b, c):
... return a + b + c
>>> _ = f(1, 2, 3)
f <== called by <module>
arguments: a=1, b=2, c=3
log_call_numbers
(default: False
)¶
log_calls keeps a running tally of the number of times a decorated callable
has been called. You can display this number using the log_call_numbers
parameter:
>>> @log_calls(log_call_numbers=True)
... def f(): pass
>>> for i in range(2): f()
f [1] <== called by <module>
f [1] ==> returning to <module>
f [2] <== called by <module>
f [2] ==> returning to <module>
The call number is also displayed with the function name when log_retval
is true:
>>> @log_calls(log_call_numbers=True, log_retval=True)
... def f():
... return 81
>>> _ = f()
f [1] <== called by <module>
f [1] return value: 81
f [1] ==> returning to <module>
The display of call numbers is particularly valuable in the presence of recursion or reentrance — see the example Indentation and call numbers with recursion, where the feature is used to good effect.
Clearing the number of calls (setting it to 0)
To reset the number of calls to a decorated function f
, so that the
next call number will be 1, call the f.stats.clear_history()
method.
To reset it for a callable in a class, call wrapper.stats.clear_history()
where wrapper
is the callable’s wrapper, obtained via one of the two methods described in
the section on accessing the wrappers of methods.
See The stats.clear_history(max_history=0) method in the Call History and Statistics chapter for details
about the clear_history()
method and, more generally, the stats
attribute.
log_elapsed
(default: False
)¶
For performance profiling, you can measure the time a callable took to execute
by using the log_elapsed
parameter. When this setting is true, log_calls
reports how long it took the decorated callable to complete, in seconds.
Two measurements are reported:
elapsed time (system-wide, including time elapsed during sleep), given by
time.perf_counter()
, andprocess time (system + CPU time, i.e. kernel + user time, sleep time excluded), given by
time.process_time()
.>>> @log_calls(log_elapsed=True) ... def f(n): ... for i in range(n): ... # do something nontrivial... ... pass >>> f(5000) f <== called by <module> arguments: n=5000 elapsed time: ... [secs], process time: ... [secs] f ==> returning to <module>
indent
(default: True
)¶
The indent
parameter, when true, indents each new level of logged messages by 4 spaces,
providing a visualization of the call hierarchy.
A decorated callable’s logged output is indented only as much as is necessary. Here, the even-numbered functions don’t indent, so the indented functions that they call are indented just one level more than their “inherited” indentation level:
>>> @log_calls()
... def g1():
... pass
>>> @log_calls(indent=False) # no extra indentation for g2
... def g2():
... g1()
>>> @log_calls()
... def g3():
... g2()
>>> @log_calls(indent=False) # no extra indentation for g4
... def g4():
... g3()
>>> @log_calls()
... def g5():
... g4()
>>> g5()
g5 <== called by <module>
g4 <== called by g5
g3 <== called by g4
g2 <== called by g3
g1 <== called by g2
g1 ==> returning to g2
g2 ==> returning to g3
g3 ==> returning to g4
g4 ==> returning to g5
g5 ==> returning to <module>
name
(default: ''
)¶
The name
parameter lets you change the “display name” of a decorated callable.
The display name is the name by which log_calls refers to the callable,
in these contexts:
- when logging a call to, and a return from, the callable
- when reporting its return value
- when it’s in a call chain.
A value provided for the name
parameter should be a string, of one of the following forms:
- the preferred name of the callable (a string literal), or
- an old-style format string with just one occurrence of
%s
, which the__name__
of the decorated callable will replace.
For example:
>>> @log_calls(name='f (STUB)')
... def f(): pass
>>> f()
f (STUB) <== called by <module>
f (STUB) ==> returning to <module>
Another simple example:
>>> @log_calls(name='"%s" (lousy name)', log_exit=False)
... def g(): pass
>>> g()
"g" (lousy name) <== called by <module>
This parameter is useful mainly to simplify the display names of inner functions, and to disambiguate the display names of getter and deleter property methods.
If the name
setting is empty (the default), the display name of a decorated callable
is its __qualname__
, followed by (a space and) its __name__
in parentheses if
the __name__
is not a substring of the __qualname__
.
Example — using name
with an inner function
The qualified names of inner functions are ungainly – in the following example,
the “qualname” of inner
is outer.<locals>.inner
:
>>> @log_calls()
... def outer():
... @log_calls()
... def inner(): pass
... inner()
>>> outer()
outer <== called by <module>
outer.<locals>.inner <== called by outer
outer.<locals>.inner ==> returning to outer
outer ==> returning to <module>
You can use the name
parameter to simplify the displayed name of the inner function:
>>> @log_calls()
... def outer():
... @log_calls(name='%s')
... def inner(): pass
... inner()
>>> outer()
outer <== called by <module>
inner <== called by outer
inner ==> returning to outer
outer ==> returning to <module>
See this section on using the name
parameter
with setter and deleter property methods, which demonstrates the use of name
to distinguish the methods of properties defined with @property
decorators.
prefix
(default: ''
)¶
The prefix keyword parameter lets you specify a string with which to prefix the name of a callable, thus giving it a new display name.
Here’s a simple example:
>>> @log_calls(prefix='--- ')
... def f(): pass
>>> f()
--- f <== called by <module>
--- f ==> returning to <module>
Because versions 0.3.0+ of log_calls use __qualname__
for the display name of
decorated callables, what had been the main use case for prefix
— prefixing method names
with their class name — has gone away. Furthermore, clearly the name
parameter can produce any
display name that prefix
can. However, prefix
is not deprecated, at least not presently:
for what it’s worth, it is a setting and can be changed dynamically, neither of which is true of name
.
file
(default: sys.stdout
)¶
The file
parameter specifies a stream (an instance of io.TextIOBase
) to which log_calls
will print its messages. This value is supplied to the file
keyword parameter of the print
function, which has the same default value. This parameter is ignored if you’ve supplied a logger
for output using the logger parameter.
When the output stream is the default sys.stdout
, log_calls always uses the current meaning
of that expression to obtain its output stream, not just what “sys.stdout” meant at program
initialization. Your program can capture, change and redirect sys.stdout
, and log_calls will
write to that stream, whatever it currently is. (doctest is a good example of a program which
manipulates sys.stdout
dynamically.)
If your program writes to the console a lot, you may not want log_calls messages interspersed
with your real output: your understanding of both logically distinct streams might be hindered,
and it may be better to make them actually distinct. Splitting off the log_calls
output can also be useful for understanding or for creating documentation: you can gather all,
and only all, of the log_calls messages in one place. The indent
setting will be respected,
whether messages go to the console or to a file.
It’s not easy to test this feature with doctest, so we’ll just give an example of writing to
sys.stderr
, and then reproduce the output:
import sys
@log_calls(file=sys.stderr)
def f(n):
if n <= 0:
return 'a'
return '(%s)' % f(n-1)
Calling f(2)
returns '((a))'
and writes the following to sys.stderr
:
f <== called by <module>
arguments: n=2
f <== called by f
arguments: n=1
f <== called by f
arguments: n=0
f ==> returning to f
f ==> returning to f
f ==> returning to <module>
mute
(default: log_calls.MUTE.NOTHING
)¶
The mute parameter gives you control over log_calls output from a given decorated callable. It can take any of the following three numeric values, shown here in increasing order:
log_calls.MUTE.NOTHING : | |
---|---|
(default) doesn’t mute any output | |
log_calls.MUTE.CALLS : | |
mutes all logging of function/method call details, but the output of any calls to the methods log_calls.print() and log_calls.print_exprs() is allowed through | |
log_calls.MUTE.ALL : | |
mutes all output of log_calls. |
mute
is a setting — part of the state maintained for a decorated callable —
and can be changed dynamically.
Examples are best deferred until the log_*()
methods are discussed:
see Indent-aware writing methods and muting — examples.
The mute
parameter lets log_calls behave just like the record_history decorator,
collecting statistics silently which are accessible via the stats
attribute of
a decorated callable. See The record_history Decorator for a precise statement of the analogy;
see the tests/examples in tests/test_log_calls_as_record_history
for illustration.
The global mute switch log_calls.mute
(default: log_calls.MUTE.NOTHING
)¶
In addition to the mute
settings maintained for each decorated callable, log_calls
also has a single class attribute log_calls.mute
. It can assume the same three
values log_calls.MUTE.NOTHING
, log_calls.MUTE.CALLS
, and log_calls.MUTE.ALL
.
Before each write originating from a call to a decorated callable, log_calls uses
the max of log_calls.mute
and the callable’s mute
setting to determine whether
to output anything. Thus, realtime changes to log_calls.mute
take effect immediately.
To see this in action, refer to Indent-aware writing methods and muting — examples.
settings
(default: None
)¶
The settings
parameter lets you collect several keyword parameter/value pairs
in one place and pass them to log_calls with a single parameter.
settings
is a useful shorthand if you have, for example, a module with several
log_calls-decorated functions, all with multiple, mostly identical settings
which differ from log_calls‘s defaults. Instead of repeating multiple identical
settings across several uses of the decorator, a tedious and error-prone practice,
you can gather them all into one dict
or text file, and use the settings
parameter to concisely specify them all en masse. You can use different groups
of settings for different sets of functions, or classes, or modules — you’re
free to organize them as you please.
When not None
, the settings
parameter can be either a dict
, or a str
specifying the location of a settings file — a text file containing key=value
pairs and optional comments. (Details about settings files, their location and their
format appear below, in settings as a pathname (str).) In either case, the valid keys are
the keyword parameters that are “settings”
(as defined in What is a setting?) plus, as a convenience, NO_DECO
.
Invalid keys are ignored.
The values of settings specified in the dictionary or settings file override log_calls‘s default values for those settings, and any of the resulting settings are in turn overridden by corresponding keywords passed directly to the decorator. Of course, you don’t have to provide a value for every valid key.
Note
The values can also be indirect values for parameters that allow indirection (almost all do), as described in the chapter Indirect Values of Keyword Parameters.
settings
as a dict
¶
The value of settings
can be a dict
, or more generally any object d
for which
it’s true that isinstance(d, dict)
. A simple example should suffice. Here is a
settings dict
and two log_calls-decorated functions using it:
>>> d = dict(
... args_sep=' | ',
... log_args=False,
... log_call_numbers=True,
... )
>>> @log_calls(settings=d)
... def f(n):
... if n <= 0: return
... f(n-1)
>>> @log_calls(settings=d, log_args=True)
... def g(s, t): print(s + t)
>>> f(2)
f [1] <== called by <module>
f [2] <== called by f [1]
f [3] <== called by f [2]
f [3] ==> returning to f [2]
f [2] ==> returning to f [1]
f [1] ==> returning to <module>
>>> g('aaa', 'bbb')
g [1] <== called by <module>
arguments: s='aaa' | t='bbb'
aaabbb
g [1] ==> returning to <module>
settings
as a pathname (str
)¶
When the value of the settings
parameter is a str
, it must be a path to a
settings file — a text file containing key=value pairs and optional comments.
If the pathname is just a directory, log_calls looks there for a file
named .log_calls
and uses that as a settings file; if the pathname is a file,
log_calls uses that file. In either case, if the file doesn’t exist then no error
results nor is any warning issued, and the settings
parameter is ignored.
Format of a settings file
A settings file is a text file containing zero or more lines of the form
setting_name=
value
Whitespace is permitted around setting_name and value, and is stripped.
Blank lines are ignored, as are lines whose first non-whitespace character is #
,
which therefore you can use as comments.
Here are the allowed “direct” values for settings in a settings file:
Setting
|
Allowed “direct” value
|
---|---|
log_args log_retval log_elapsed log_exit indent log_call_numbers record_history NO_DECO |
boolean
(case-insensitive –
True , False , tRuE , FALSE , etc.) |
enabled |
int, or case-insensitive boolean as above
|
args_sep prefix |
string, enclosed in quotes
|
loglevel max_history |
int
|
file |
sys.stdout or sys.stderr ,not enclosed in quotes (or
None ) |
logger |
name of a logger, enclosed in quotes (or
None ) |
Warning
Ill-formed lines, bad values, and nonexistent settings are all ignored, silently.
Settings file example
Here’s an example of what a settings file might contain:
args_sep = ' | '
log_args = False
log_retval = TRUE
logger = 'my_logger'
# file: this is just for illustration, as logger takes precedence.
# file can only be sys.stderr or sys.stdout [*** NOT IN QUOTES! ***] (or None)
file=sys.stderr
# ``log_elapsed`` has an indirect value:
log_elapsed='elapsed_='
# The following lines are bad in one way or another, and are ignored:
prefix=1492
loglevel=
no_such_setting=True
indent
Note
You can use the log_calls.set_defaults()
classmethod to change the log_calls default settings,
instead of passing the same settings
argument to every @log_calls(...)
decoration.
See the chapter Retrieving and Changing the Defaults.
Where to find more examples¶
The test file tests/test_log_call_more.py
, in the docstring of the function
main__settings()
, contains several examples (doctests) of the settings
parameter.
Two of the tests there use “good” settings files in the tests/
directory: .log_calls
and log_calls-settings.txt
. Two more test what happens (nothing) when specifying
a nonexistent file or a file with “bad” settings (tests/bad-settings.txt
).
Another tests the settings
parameter as a dict
.
NO_DECO
(default: None
)¶
The NO_DECO
parameter prevents log_calls from decorating a callable or class:
when true, the decorator returns the decorated thing itself, unwrapped and unaltered.
Intended for use at program startup, it provides a single “true bypass” switch.
Using this parameter in a settings dict or settings file lets you control “true bypass” with a single switch, e.g. for production, without having to comment out every decoration.
NO_DECO
can only prevent decoration, it cannot undo decoration.
For example, if f
is already decorated, then:
f = log_calls(NO_DECO=True)(f)
has no effect: f
remains decorated.
Use NO_DECO=True
for production¶
Even even when it’s disabled or bypassed, log_calls imposes some overhead.
For production, therefore, it’s best to not use it at all. One tedious way to guarantee
that would be to comment out every @log_calls()
decoration in every source file.
NO_DECO
allows a more humane approach: Use a settings file or settings dict
containing project-wide settings, including an entry for NO_DECO
. For development, use:
NO_DECO=False
and for production, change that to:
NO_DECO=True
Even though it isn’t actually a “setting”, NO_DECO
is permitted in settings files and dicts
in order to allow this.
Examples¶
The tests in tests/test_no_deco__via_file.py
demonstrate using NO_DECO
in an imported dict
and in a settings file.
override
(default: False
)¶
The override
parameter is mainly intended for use when redecorating functions and classes
with the log_calls.decorate_*
classmethods, as discussed in the chapter
Bulk (Re)Decoration, (Re)Decorating Imports.
override
can also be used with class decorators to give its settings precedence over
any explicitly given for callables or inner classes. See Precedence of inner decorators over outer decorators for
a simple example, and Example — decorating a class in scikit-learn for a larger one.
Parameters Documented In Other Chapters¶
The remaining parameters are more specialized and require discussion of the contexts in which they are used. For completeness, we catalog them here, together with links to their documentation.
omit
, only
(defaults: tuple()
)¶
In the chapter Decorating Classes, the section The omit and only keyword parameters (default: ()) documents the two parameters that control which callables of a class get decorated. The value of each is a string or a sequence of strings; each string is either the name of a callable, or a “glob” pattern matching names of callables.
omit
— log_calls will not decorate these callables;only
— log_calls decorates only these callables, excluding any specified byomit
logger
(default: None
), loglevel
(default: logging.DEBUG
)¶
Using Loggers presents the two parameters that let you output log_calls messages to a Logger
:
record_history
(default: False
), max_history
(default: 0
)¶
Call History and Statistics discusses the two parameters governing call history collection:
- record_history governs whether call history is retained, and then
- max_history controls how much (cache size).